一个现代编译器的主要工作流程如下:
源程序(source code)→预处理器(preprocessor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→连接器(链接器,Linker)→可执行程序(executables)。
简言之,源文件生成可执行文件的过程总共是经历了预编译/预处理,编译,汇编,链接这四个过程。
如下图所示:
下面用源文件test.c进行解释,test.c中代码为:
#include
int main()
{
printf("hello world!\n");
return 0;
}
如输入命令:gcc test.c
则会完成上述四个过程,直接默认生成可执行文件a.out
如下图所示:
gcc参数如下:
下面分别对上述四个过程进行说明。
命令:gcc -E test.c > test.i 或 gcc -E test.c -o test.i
注:只有预编译/预处理可以>重定向,因为 gcc -E test.c的结果会在终端显示而不生成文件。
其实预编译就是我们所说的预处理。
预编译/预处理主要作用:
处理关于 “#” 的指令
【1】删除#define,展开所有宏定义。例#define portnumber 8080
【2】处理条件预编译 #if, #ifdef, #if, #elif,#endif
【3】处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。
【4】删除所有注释。/**/,//。
【5】添加行号和文件标识符。用于显示调试信息:错误或警告的位置。
【6】保留#pragma编译器指令。(1)设定编译器状态,(2)指示编译器完成一些特定的动作。
在四个过程中,第一个进行的是预编译的过程。
接下来,我们一步步详细讨论下其中发生的:
第一步发生的是预编译,在这我们使用-E指令,使用这个指令会使程序只进行到预编译指令。把预编译的结果放到test.i文件中。在预编译的过程中,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏的替换,添加行号,保留所有的编译器指令。
下图就是预编译后得到的结果 。
所以当进行预编译以后的文件中将不再存在宏,所有的宏都已经被替代。当我们想要判断宏是否正确或者头文件包含是否正确的时候,我们也可以通过预编译来查看。
命令:gcc -S test.c -o test.s 或 gcc -S test.i -o test.s
注:gcc -S test.c 或 gcc -S test.i 会默认生成test.s文件。
在预处理结束后,我们所要进行的就是编译。编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码文件test.s。
test.s打开以后的结果是:
从结果我们可以知道,得到的是汇编代码。
命令:gcc -c test.c -o test.o 或 gcc -c test.s -o test.o 或 gcc -c test.i -o test.o
注1:gcc -c test.c 或 gcc -c test.s 或 gcc -c test.i 会默认生成test.o文件。
注2:gcc -c x1.c x2.c 结果将生成:两个目标文件x1.o x2.o
这里的汇编所说的是一个过程,将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。相当于windows下的.obj文件。这里生成的目标文件里面就是二进制文件。另外,在这需要注意的是,会形成符号表,给这些符号会分配虚拟地址。
命令:gcc test1.c test2.c -o test 或 gcc test1.o test2.o -o test
链接,其实就是将二进制文件链接成为一个可执行的指令。
链接所完成的任务是合并段表,然后把符号表合并并且对符号表进行重定位。
所谓合并段表,源代码编译生成的a.out会包含很多段,数据段文本段bss段等等,这些段是合并出来的,在编译过程中划分出来的,不同的数据会对应到不同的段中,在.o文件中其实已经发生了分段。
符号表合并和重定位说的是最后只生成了一个符号表,这个符号表是由前面汇编形成的多个符号表进行合并。在这里不在同一个符号表的符号,要对他们进行重定位。
补充1:Microsoft Visual Studio中的源代码到可执行程序exe如下图所示:
图中“编译器”(相当于VS中的编译Ctrl+F7),其实包含了预处理,编译,汇编三个过程。
Microsoft Visual Studio中的编译(Ctrl+F7)相当于前述预处理,编译,汇编三个过程,将生成obj目标文件。但不会生成exe文件。
我想这也是大部分人将“编译”理解为生成obj文件的原因。其实汇编才是生成obj文件,编译是生成汇编代码文件。
补充2:汇编和反汇编的区别
汇编是将汇编语言翻译成机器语言的过程。 也就是test.s文件经过汇编器变成二进制目标文件test.o文件的过程。
反汇编指的是由已生成的机器语言(二进制语言)转化为汇编语言的过程,也就是汇编的逆向过程。
在Linux下利用反汇编器对.o文件进行反汇编。 如下图所示。
命令: objdump -d test.o
可看出和test.s中内容几乎一样!