C/C++源程序到可执行程序exe的全过程(及汇编和反汇编的区别)

一个现代编译器的主要工作流程如下:

源程序(source code)→预处理器(preprocessor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→连接器(链接器,Linker)→可执行程序(executables)。

简言之,源文件生成可执行文件的过程总共是经历了预编译/预处理,编译,汇编,链接这四个过程。

如下图所示:

下面用源文件test.c进行解释,test.c中代码为:

#include <stdio.h>
int main()
{
       printf("hello world!\n");
       return 0;
}

如输入命令:gcc test.c 

则会完成上述四个过程,直接默认生成可执行文件a.out

如下图所示:

gcc参数如下:

下面分别对上述四个过程进行说明。

1.预编译/预处理——生成test.i

命令: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文件中。在预编译的过程中,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏的替换,添加行号,保留所有的编译器指令。

下图就是预编译后得到的结果 。

所以当进行预编译以后的文件中将不再存在宏,所有的宏都已经被替代。当我们想要判断宏是否正确或者头文件包含是否正确的时候,我们也可以通过预编译来查看。

2.编译——生成汇编代码文件test.s

命令: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打开以后的结果是:

从结果我们可以知道,得到的是汇编代码。

3.汇编——生成二进制目标文件test.o

命令: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文件。这里生成的目标文件里面就是二进制文件。另外,在这需要注意的是,会形成符号表,给这些符号会分配虚拟地址。

4.链接——生成默认a.out

命令: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中内容几乎一样!

posted @ 2019-05-12 19:32  Adano1  阅读(1019)  评论(0编辑  收藏  举报