02编译和链接
本文主要介绍编译器是如何编译和链接程序的
1. 预编译:
gcc -E hello.c -o hello.i
主要工作如下:
- 删除所有
#define
展开所有宏定义 - 处理所有条件编译, 比如
#if
,#elif
,#else
,#endif
- 处理
#include
, 递归地将被包含地文件插入到该指令位置 - 删除所有注释
- 添加行号和文件名标识以便调试和编译出错显示
- 保留所有
#pragram
编译指令
当我们无法确定宏定义或头文件是否正确时可直接查看预编译输出得到结果
2. 编译
gcc -S hello.i -o hello.s
gcc -S hello.c -o hello.s
编译分为
- 词法分析, 通过一个扫描器 (Scanner) 输出一个个有意义的记号 (Tokens), 利用一个叫 lex 程序可简单地实现该功能
- 语法分析, 对扫描器产生地记号进行语法分析, 生成语法树, 其实现可参考数据结构与算法里的二叉树部分
- 语义分析, 对语法树每个节点进行语义分析, 对类型做隐式转换等
- 中间代码生成, 该中间代码与机器代码无关, 至此编译器前端工作完成, 后续由编译器后端完成代码的处理和优化
- 目标代码生成与优化, 该部分由编译器后端完成, 包括代码生成器和优化器, 现代 CPU 的复杂, 导致编译器指令生成过程愈加复杂
3. 汇编
as hello.s -o hello.o
gcc -c hello.s -o hello.o
当然也可以直接使用 gcc 从 C 源文件直接到目标文件
gcc -c hello.c -o hello.o
4. 链接
每一个独立的 .c 文件都将被编译生成一个独立的 .o 目标文件, 这些目标文件可以被看做是一个个独立的模块, 这些模块之间的通信有两种:
- 一种是模块间的函数调用
- 另一种是模块间的变量访问
这两种方式都可归结为模块间符号 (Symbol) 的引用, 而链接的主要工作就是把各个模块之间相互引用部分都处理好, 使得各个模块之间能够正确地衔接.
链接过程主要包括地址和空间分配, 符号决议. 和重定位等步骤.
例如:
ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lc -end-group crtend.o crtn.o
除了我们自己编写地 hello.o 目标文件外, 还需链接运行时库
crt*.o
和 C 标准库 libc.a
等才能运行,
这些库其实也是一组目标文件,
人们为了方便将它们打包存放到了连接器能查找的环境变量中
评论