一、构建过程
预编译
gcc -E source.c -o source.i
处理以 “#” 开始的预编译指令
- 删除所有的 “#define”, 展开所有的宏定义。
- 处理所有条件预编译指令,“#if”、“#ifdef”。
- 处理 “#include” 指令,将包含的文件插入到预编译指令的位置。
- 删除注释
- 保留所有的 #pragma 编译器指令。
- 添加行号和文件名标识
源代码
#include <stdio.h>
#pragma pack(4)
#define PI 3.1415926
int main()
{
#ifdef WINOS
printf("WIN OS\n");
#else
printf("MAC OS\n");
#endif
printf("PI = %f\n", PI);
printf("Hello World!\n");
return 0;
}
预编译后
······
······
# 2 "source.c" 2
#pragma pack(4)
int main()
{
printf("MAC OS\n");
printf("PI = %f\n", 3.1415926);
printf("Hello World!\n");
return 0;
}
编译
将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成汇编代码文件。 编译是以C文件为编译单元的
gcc -S source.i -o source.s
汇编
将汇编代码转为机器指令,产生目标文件。
gcc -c source.s -o source.o
链接
若干变量和函数组成一个模块,一个源文件称为一个编译单元,链接主要是处理各个编译单元之间的符号引用。 包括地址和空间分配、符号决议、重定位。
二、编译过程
- 词法分析
- 将源代码分解为单词符号。
- 语法分析
- 上下文无关算法。
- 生成语法树。
- 检查语法错误(括号不匹配、缺少操作符等)。
- 语义分析
- 静态语义分析(编译器可以确定的语义)。
- 检查语义错误(声明和类型的匹配)。
- 优化
- 源码级优化:
- 语法树转换成中间代码(三地址码)。
- 将一些编译期可以确定值的代码优化掉,例如表达式 2 + 6 会被优化为数值 8。
- 产生的中间代码是机器无关的。
- 代码生成器:
- 将中间代码转换为目标机器代码。
- 目标机器代码优化
- 源码级优化:
三、链接(静态链接)的基本过程
重定位 编译单元 main.c 引用了另一个编译单元 func.c 中的 foo() 函数。在编译 main.c 的时候并不知道 foo() 的具体地址,指令中 foo() 的地址就会被搁置。链接的时候,链接器会去 func 模块查找 foo() 的真实地址,并将 main 当中引用 foo() 的指令中的地址替换为真实地址。