1 隐藏的事实
编译器是由几部分组成的,包括预处理器,编译器,汇编器,链接器。
我们写的代码经过预处理器,变成 .i 文件,再经过编译器处理,变成 .s 文件,再经汇编器,生成 .o 文件,最后经链接器,变成可执行文件。
预处理器,编译器,汇编器,链接器都做了什么呢,我们接着看
2 预编译
- 处理所有的注释,以空格代替
- 将所有的 #define 删除,并且展开所有的宏定义
- 处理条件编译指令 #if,#ifdef,#elif,#else,#endif
- 处理 #include,展开被包含的文件
- 保留编译器需要使用的 #pragma
预处理指令实例:gcc -E file.c -o file.i
/*
19-1.h
This is a header file.
*/
char* p = "hello";
int i = 0;
// 19-1.c
#include"19-1.h"
// begin to define macro
#define GREET "hello world!"
#define INC(x) x++
// end
int main(){
p = GREET;
INC(i);
return 0;
}
gcc -E 19-1.c -o 19-1.i
得文件 19-1.i 文件,下面是 19-1.i 文件
# 1 "19-1.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "19-1.c"
# 1 "19-1.h" 1
char* p = "hello";
int i = 0;
# 3 "19-1.c" 2
int main(){
p = "hello world!";
i++;
return 0;
}
可以看到,注释被直接去除,头文件被直接展开,宏定义被直接展开。以 # 开头的信息是给后续编译器其他模块使用的。
3 编译
- 对预处理文件进行词法分析,语法分析和语义分析
- 词法分析:分析关键字,标示符,立即数等是否合法
- 语法分析:分析表达式是否遵循语法规则
- 语义分析:在语法分析的基础上进一步分析表达式是否合法
- 分析结束后进行代码优化生成相应的汇编代码文件
编译指令实例:gcc -S file.i -o file.s
gcc -S 19-1.i -o 19-1.s
生成 19-1.s 如下,里面的汇编代码这里不做解析了。
4 汇编
- 汇编器将汇编代码转变为机器的可以执行执行
- 每条汇编语句几乎都有对应一条机器指令
汇编指令示例:gcc -c file.s -o file.o
gcc -c 19-1.s -o 19-1.o
生成文件19-1.o,这里还不是可执行文件。
经过链接器处理才能生成可执行文件:gcc 19-1.o -o 19-1
5 小结
编译过程分为预处理,编译,汇编和链接四个阶段
1、预处理:处理注释,宏以及以 # 开头的符号
2、编译:进行词法分析,语法分析和语义分析等
3、汇编:将汇编代码翻译成机器指令的目标文件