编译汇编原理
我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为可执行程序;
那么问题来了,可执行程序是怎样生成的呢?
gcc 编译过程
从 main.c 到 main(或 a.out)文件, 必须历经 main.i、 main.s、 main.o,最后才得到 main(或a.out)文件,分别对应着预处理、编译、汇编和链接 4 个步骤,整个过
程如图所示:
一、预处理
编译器对各种预处理命令进行处理,包括头文件包含、宏定义的扩展、条件编译的选择等;
具体作用为:处理关于 “#” 的指令
(1)删除#define,展开所有宏定义。例#define portnumber 3333
(2)处理条件预编译 #if, #ifdef, #if, #elif,#endif
(3)处理“#include”预编译指令,将包含的“.h”文件插入对应位置。
(4)删除所有注释/**/,//。
(5)添加行号和文件名标识,比如:#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号。
(6)保留所有的#pragma编译器指令,因为编译器需要它们。
查看main.i文件:
二、编译
预处理之后,可直接对生成的main.i文件编译,生成汇编代码:
主要作用:1.扫描(词法分析),2.语法分析,3.语义分析,4.源代码优化(中间语言生成),5.代码生成,目标代码优化
【1】将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。例array[index] = (index + 4) * (2 + 6);
【2】基于词法分析得到的一系列记号,生成语法树。
【3】由语义分析器完成,指示判断是否合法,并不判断对错。又分静态语义:隐含浮点型到整形的转换,会报warning,
动态语义:在运行时才能确定:例1除以3
【4】中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。
【5】编译器后端主要包括:代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等
目标代码优化器:选择合适的寻址方式,左移右移代替乘除,删除多余指令。
查看main.s文件:
三、汇编·
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个汇编源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:
代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
数据段:主要存放程序中要用到的各种全局变量或静态的局部变量。(.rodata和 .data)
作用:汇编器是将汇编代码转变成可以执行的指令,生成目标文件。
查看main.o文件:
四、链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
根据开发人员指定的库函数的链接方式的不同,链接处理可分为两种:
(1)静态链接 在这种链接方式下,函数的代码(被应用程序引用的目标模块)将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。静态连接的劣势:浪费内存和磁盘空间,模块更新困难。
(2)动态链接 在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射(优点:无拷贝环节,在内存中只有一份此共享代码,以节约存储器空间)到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
动态连接解决了共享的目标文件多个副本浪费磁盘和内存空间的问题。在内存中共享一个目标文件模块的好处不仅仅是节省内存,还可以减少物理页面的换入换出,亦可以增加CPU的cache hit (关于这部分在《深入理解计算机系统》中有详细介绍,尤其是程序的局部性原理的应用,以前写代码都是瞎写,根本不知道还有这么个优势)。
动态连接也有其缺点:很常见的一个问题是,当程序所依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致原有的程序无法运行。
最后生成的可执行文件main: