参考书目:《嵌入式C语言自我修养》,王利涛编著。
(一)gcc编译器编译选项
常用的gcc编译选项如下:
-E 对.c程序预处理,生成.i文件
-S 对.c程序进行汇编,生成.s文件
-c 只生成目标文件(.o),不进行链接
-o 生成可执行文件(.out)
-g 生成带调试信息的debug文件
-O2/O3/O1/Ofast 开始编译优化选项,设置对应的编译优化等级(一般使用O2,Ofast包含O3全部的优化选项,并且不严格遵守语言标准)
-I(大写的i) 指定头文件路径
-l(小写的L) 指定使用的函数库
-L 指定函数库的路径
(二)可执行文件的内部结构
一个可执行文件通常由不同的段(section)构成:代码段、数据段、BSS段、只读数据段等。
-
代码段:函数翻译成二进制指令放在代码段中。
-
数据段:初始化的全局变量经过编译后放在数据段中。
-
BSS段:一般来讲,未初始化的全局变量和静态变量会放置在BSS段中。(由于未初始化,默认全为0,没必须单独开辟存储空间,因此在可执行文件中BSS段是不占用空间的。)
(三)程序编译和链接流程
gcc对一个程序的编译链接流程如下图所示:

预处理阶段
预处理主要包括以下操作:
-
头文件展开:将#include包含的头文件内容展开到当前位置
-
宏展开:展开所有宏定义,并删除#define
-
条件编译:根据宏定义的条件,选择要参与编译的分支代码,其余的分支丢弃
-
删除注释
-
添加行号和文件名标识:编译过程中根据需要可以显示这些信息
printf("line number : %d file: %s\n\r", __LINE__, __FILE__);
-
保留#pragma命令:该命令会在程序编译时指示编译器执行一些特定行为,如自己在程序中添加编译提示信息,则编译器在编译此文件时会在窗口输出编译提示信息。
#pragma message("build helloworld.c...\n\r")
编译阶段
编译阶段主要分为两步:
- 第一步,编译器调用一系列解析工具,将C源文件编译为汇编文件;
- 第二步,通过汇编器将汇编文件汇编成可重定位的目标文件
编译过程
编译过程可以分为6步:
-
词法分析
主要对c语言语句进行分析,通过词法扫描器从左到右,按字符读入源程序,将源程序分解为不能分解的记号单元——token。常见的token如下:
- C语言关键字,如:int、float等
- 用户定义的各种标识符,如函数名、变量名等
- 字面量:数字、字符串等
- 运算符:C语言标准定义的40多个运算符。
- 分隔符:分号、逗号等。
-
语法分析
对上一阶段的token序列进行解析,分解为一个语法上正确的语法树。例如语句
sum = a + b / c
可以分解成如下图的语法树。语法分析仅对程序做语法检查,对程序、语句的真正意义并不了解。

-
语义分析
语义分析主要对语法分析输出的各种表达式、语句进行检查。
-
生成中间代码
在语法分析阶段输出的表达式或程序语句,还是以语法树的形式存储,需要将其转换为中间代码。
-
汇编代码生成
中间代码一般和平台无关。如果在X86平台或ARM平台上运行,则根据不同的指令集,编译器将中间代码翻译成不同平台的汇编程序。
-
目标代码生成
汇编过程
汇编过程是使用汇编器将前一阶段生成的汇编文件翻译成目标文件(.o)。
*.o文件是不可执行的,属于可重定位的目标文件,即以零地址为链接起始地址进行链接的。可以使用 readelf -S
命令来观察可重定位目标文件的地址。在每个可重定位目标文件中,函数或变量的地址就是它们在文件中相对于零地址的偏移。
链接器将各个目标文件组装在一起后,重新修改各个目标文件中的变量或函数的地址,这个过程一般称为重定位。
链接过程中会生成符号表和重定位表,可以使用readelf -s
命令来查看某个.o文件的符号表信息。重定位表使用readelf -S
查看,某个.o文件中.rel.text部分就是重定位表。
链接阶段
链接阶段主要分为三个过程:分段组装、符号决议和重定位。
分段组装
将各个可重定位目标文件重新分解组装:将各个目标文件的代码段放在一起,数据段放在一起,其他section组装方法同样。
同时收集各个目标文件符号表中的符号,创建全局符号表。
符号决议
为了解决位于不同模块或不同文件中函数名、全局变量名可能重名冲突的情况。
强符号:函数名、初始化的全局变量。
弱符号:未初始化的全局变量。
决议规则:
- 不允许强符号多次定义。
- 强符号和弱符号可以共存,共存时强符号会覆盖弱符号。
- 允许多个弱符号共存。
重定位
各个目标文件中有一个重定位表,专门记录各个文件中需要重定位的符号。重定位的核心工作就是修正指令中的符号地址,是链接过程中最后、最核心、最重要的一步。
链接器读取各个目标文件中的重定位表,根据这些符号在可执行文件中的新地址,进行符号重定位,修改指令代码中引用这些符号的地址。
至此,整个编译链接流程结束,最终生成可执行目标文件(.out)。
本文地址: C语言程序的编译和链接