编译过程分为 预处理、编译、汇编,生成二进制可重定位目标文件(obj文件)!
预编译: 生成.i文件,第一步预编译的过程相当于如下命令: gcc -E hello.c -o hello.i (假定文件名为hello.c)
预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令。比如“#include"、”#define"等,主要的处理规则如下:
- 删除所有的“#define"并进行文本替换,即展开所有的宏定义;
- 处理所有的条件预编译指令,如“#if"、"#endif"、"#elif"、"#ifdef"、"#else";
- 递归展开"#include"预编译指令;
- 删除所有的注释"//"和"/* */";
- 添加行号和文件标识。便于编译器编译时产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号
- 保留所有的 #pragma 编译器指令,因为编译器需要使用它们 (qaq)
当我们无法判断宏定义是否正确或者头文件包含是否正确时,可以查看预编译后的文件来确定问题!
编译:生成.s文件,编译过程相当于如下命令:gcc -S hello.i -o hello.s
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析、代码优化以及优化后生成相应的汇编代码文件
汇编:生成 .o 文件,汇编过程相当于如下指令: gcc -c hello.s -o hello.o 或者直接输出目标文件 gcc -c hello.c -o hello.o
汇编是将汇编代码转变成机器可以执行的指令,即翻译指令
链接:生成 .exe 文件
链接主要包含 :
- 合并段和符号表
- 符号解析
- 分配地址和空间
- 符号重定位
运行:
- 建立虚拟地址空间和物理内存的映射(创建映射结构 PCB)创建页目录,页表
- 加载指令和数据
- 入口地址写入下一行指令寄存器
为了构造可执行文件,链接器必须完成两个主要任务:
符号解析。目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用正好和一个符号定义关联起来。
重定位。编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,是的它们指向这个内存位置。
目标文件三种形式:
可重定位目标文件、可执行目标文件、共享目标文件。
编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件。
可重定位目标文件:
.data : 只读数据
.data : 已初始化的全局和静态C变量。局部C变量在运行时被保存在栈中,既不出现在 .data节,也不出现在 .bss节中。
.bss: 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符,目标文件格式区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。
可执行目标文件:
可执行目标文件的格式类似于可重定位目标文件的格式。.init节定义了一个小函数,叫做_init,程序的初始化代码会调用它。因为可执行文件是完全连接的(已被重定位),所以它不在需要 .rel节。
深入了解编译连接运行原理可阅读《深入理解计算机系统》第7章,主要讲了链接~
《程序员的自我修养---链接、装载与库》第2、3、4主要讲编译链接是具体内容。
一只菜鸡的小笔记!!!