单片机编译文件与内存段

最近都在研究EC单片机,很久没接触比较成熟的单片机工程了,发现很多基础性的内容都很模糊了。花时间又找资料重温了下。

记得刚入门单片机时候,经常会被编译后的各种文件格式、内存段划分搞得很迷糊。比如.bin.hex有什么区别?.text.bss段那些又是什么?

本文简单梳理介绍一下。

常见文件格式解析

我们用Keil、IAR等编译器编写完C语言代码,点击编译后,工程目录下会生成多种不同后缀的文件。这些文件有不同作用,有的用于调试,有的用于烧录。

文件后缀 全称/类型 核心含义&用途 关键特点
.elf ExecutableandLinkableFormat(可执行链接格式) 编译链接后的核心中间文件,包含程序的所有信息:代码段(.text)、数据段(.data/.bss)符号表、调试信息、函数/变量地址未剥离的原始程序结构 体积最大,不直接烧录,用于调试(如J-Link/ST-Link)、转换为bin/hex
.lst ListFile(列表文件) 汇编/反汇编列表文件,是代码的“可视化清单”:每行对应地址、机器码、汇编指令;关联C代码和汇编指令;标注段信息、行号 纯文本格式,用于调试(定位代码对应的机器码/地址),无烧录价值
.map MapFile(映射文件) 链接器生成的内存分布报告:程序各段(RO/RW/ZI)的地址、大小;每个函数/模块占用的Flash/RAM空间;符号(函数/变量)的最终地址 核心用途:检查Flash/RAM占用情况,排查内存溢出/地址冲突
.bin BinaryFile(二进制文件) 纯二进制数据文件,仅包含:实际要烧录到Flash的机器码(.text)初始化数据(.data)无任何额外信息(地址、校验、文本等) 体积最小,代表程序实际占用的Flash物理空间,是烧录的“原始数据”
.hex IntelHEXFile(Intel格式十六进制文件) ASCII文本格式的烧录文件,基于bin文件扩展:把二进制数据转换成ASCII字符(如0x12→”12”)包含地址、数据长度、校验和支持分段地址(适配大容量Flash) 文本编码+额外信息,体积远大于bin,是烧录工具(如JFlash)的通用输入格式

HEX文件它自带地址信息、所以一般烧录时不需要指明烧录地址。bin文件则没有地址信息,所以一般需要指明烧录地址。

通用核心段(Section)

单片机的程序和数据存储在不同的内存区域,这些区域被称为“段”(Section)。不同的段有不同的用途、存储位置(Flash/RAM)和特性,我们也可以自定义段。

1.代码段:.text(最核心)
作用:存放程序的可执行代码(所有函数、指令、汇编指令),比如主函数main()、普通函数、中断函数的指令部分。
存储位置:单片机的Flash(只读存储器),掉电不丢失。
特点:只读、占用Flash空间,是程序的“执行主体”。

2.已初始化数据段:.data
作用:存放初始化过的全局变量、静态变量(比如inta = 10;、staticfloatb = 3.14;)。
存储位置:程序烧录时先存在Flash,单片机上电后,启动代码会把这部分数据复制到RAM中运行(因为RAM可读写,Flash只读)。
特点:可读写、占用RAM空间,掉电丢失(RAM特性)。

3.未初始化数据段:.bss
作用:存放未初始化的全局变量、静态变量(比如intc;、staticchard;),或初始化为0的全局/静态变量(inte = 0;)。
存储位置:仅占用RAM空间,Flash中只记录“需要清零的大小”,上电后启动代码会自动把这部分RAM区域清零。
特点:可读写、不占用Flash空间(仅占RAM),默认值为0。

4.只读数据段:.rodata
作用:存放只读常量,比如字符串常量(”helloworld”)、const修饰的变量(constintf = 5;)。
存储位置:Flash(只读),掉电不丢失。
特点:只读、避免RAM浪费(常量无需放RAM)。

运行时关键段(RAM中动态使用)

1.栈段:.stack(栈)
作用:存放函数局部变量、函数参数、返回地址、寄存器临时值,比如函数内的intx = 1;、函数调用时的参数传递。
存储位置:RAM,先进后出(LIFO),有固定的栈顶指针(SP)。
特点:自动分配/释放(编译器管理),栈溢出会导致程序崩溃(需在链接脚本中配置合适的栈大小)。

2.堆段:.heap(堆)
作用:用于动态内存分配(比如C语言的malloc()、free(),C++的new/delete)。
存储位置:RAM,与栈相对,从低地址向高地址增长(栈是高地址向低地址)。
特点:程序员手动管理,容易出现内存泄漏、碎片,单片机中慎用(无内存管理单元MMU)。

常见特殊段(根据架构/场景扩展)

除了.task,这些段在实际开发中也很常见:
1.中断相关段
.vector/.isr_vector:中断向量表段,存放所有中断/异常的入口地址(比如复位、定时器中断、串口中断的函数地址),必须放在Flash的固定地址(单片机硬件规定),是程序上电的“入口指引”。

.irq/.isr:中断服务程序段,专门存放中断处理函数的代码(部分编译器拆分,也可合并到.text)。

2.初始化/终止段
.init:程序启动初始化段,存放上电后main()执行前的初始化代码(比如系统时钟配置、RAM初始化、.data/.bss段的初始化)。

.fini:程序终止段,存放程序退出时的清理代码(单片机中极少用到,多为裸机/RTOS常驻运行)。

3.自定义段
.cmd_func:可用于将特定函数或变量固定到指定内存地址,适配特殊硬件需求(如外设寄存器映射、中断函数固定地址等)。

我们可以自定义段,比如:

1
2
3
4
5
6
7
8
9
10
// 示例:自定义段(GCC 编译器)
// 定义存放至 .initial 段的属性宏
#define _INITIAL __attribute__((unused, section(".initial")))

// 定义存放至 .task 段的属性宏
#define _TASK __attribute__((unused, section(".task")))

// 初始化钩子声明宏
#define HOOK_DECLARE_INIT(func, level, enable) \
init_t _fn_##func _INITIAL = {func, level, enable};

通过编译器指令,手动将变量/函数放到指定内存地址,从而适配特殊硬件场景。一般会在比较成熟的工程中使用。