C语言[yu yan]编译[bian yi]过程总结详解(转) 本来打算把编译[bian yi]部分的内容总结一下,偶然看到一位网友的文章写得很全面,故此稍加整理,以作备忘。
C语言[yu yan]的编译[bian yi]链接[lian jie]过程要把我们编写的一个c程序(源代码[dai ma][yuan dai ma])转换[zhuan huan]成可以在硬件[ying jian]上运行[yun hang]的程序(可执行[zhi hang]代码[dai ma]),需要进行编译[bian yi]和链接[lian jie]。编译[bian yi]就是把文本[wen ben]形式源代码[dai ma][yuan dai ma]翻译为机器语言[yu yan][ji qi yu yan]形式的目标[mu biao]文件[wen jian]的过程。链接[lian jie]是把目标[mu biao]文件[wen jian]、操作系统[xi tong][cao zuo xi tong]的启动[qi dong]代码[dai ma]和用到的库文件[wen jian]进行组织形成最终生成可执行[zhi hang]代码[dai ma]的过程。过程图解如下:
从图上可以看到,整个代码[dai ma]的编译[bian yi]过程分为编译[bian yi]和链接[lian jie]两个过程,编译[bian yi]对应图中的大括号括起的部分,其余则为链接[lian jie]过程。
编译[bian yi]过程
编译[bian yi]过程又可以分成两个阶段:编译[bian yi]和会汇编[hui bian]。
编译[bian yi]
编译[bian yi]是读取[du qu]源程序[yuan cheng xu](字符[zi fu]流),对之进行词法[ci fa]和语法[yu fa]的分析,将高级语言[yu yan][gao ji yu yan]指令[zhi ling]转换[zhuan huan]为功能等效的汇编[hui bian]代码[dai ma],源文件[wen jian]的编译[bian yi]过程包含两个主要阶段:
第一个阶段是预处理阶段,在正式的编译[bian yi]阶段之前进行。预处理阶段将根据已放置在文件[wen jian]中的预处理指令[zhi ling]来修改[xiu gai]源文件[wen jian]的内容。如#include指令[zhi ling]就是一个预处理指令[zhi ling],它把头文件[wen jian]的内容添加到.cpp文件[wen jian]中。这个在编译[bian yi]之前修改[xiu gai]源文件[wen jian]的方式提供了很大的灵活性[ling huo xing],以适应不同的计算机和操作系统[xi tong][cao zuo xi tong]环境[xi tong huan jing]的限制。一个环境需要的代码[dai ma]跟另一个环境所需的代码[dai ma]可能有所不同,因为可用的硬件[ying jian]或操作系统[xi tong][cao zuo xi tong]是不同的。在许多情况[qing kuang]下,可以把用于[yong yu]不同环境的代码[dai ma]放在同一个文件[wen jian]中,再在预处理阶段修改[xiu gai]代码[dai ma],使之适应当前的环境。
主要是以下几方面的处理:
(1)宏定义指令[zhi ling],如 #define a b
对于这种伪指令[zhi ling][wei zhi ling],预编译[bian yi][yu bian yi]所要做的是将程序中的所有a用b替换[ti huan],但作为字符[zi fu]串[zi fu chuan]常量[chang liang]的 a则不被替换[ti huan]。还有 #undef,则将取消[qu xiao]对某个宏的定义,使以后该串的出现不再被替换[ti huan]。
(2)条件[tiao jian]编译[bian yi]指令[zhi ling],如#ifdef,#ifndef,#else,#elif,#endif等。
这些伪指令[zhi ling][wei zhi ling]的引入使得程序员[cheng xu yuan]可以通过定义不同的宏来决定编译[bian yi]程序[bian yi cheng xu]对哪些代码[dai ma]进行处理。预编译[bian yi][yu bian yi]程序[bian yi cheng xu][yu bian yi cheng xu]将根据有关的文件[wen jian],将那些不必要的代码[dai ma]过滤[guo lv]掉。
(3) 头文件[wen jian]包含指令[zhi ling],如#include "FileName"或者#include <FileName>等。
在头文件[wen jian]中一般用伪指令[zhi ling][wei zhi ling]#define定义了大量的宏(最常见的是字符[zi fu]常量[chang liang]),同时包含有各种外部符号的声明[sheng ming]。采用头文件[wen jian]的目的主要是为了使某些定义可以供多个不同的C源程序[yuan cheng xu]使用。因为在需要用到这些定义的C源程序[yuan cheng xu]中,只需加上一条#include语句[yu ju]即可,而不必再在此文件[wen jian]中将这些定义重复一遍。预编译[bian yi][yu bian yi]程序[bian yi cheng xu][yu bian yi cheng xu]将把头文件[wen jian]中的定义统统都加入到它所产生的输出[shu chu]文件[wen jian]中,以供编译[bian yi]程序[bian yi cheng xu]对之进行处理。包含到c源程序[yuan cheng xu]中的头文件[wen jian]可以是系统[xi tong]提供的,这些头文件[wen jian]一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件[wen jian],这些文件[wen jian]一般与c源程序[yuan cheng xu]放在同一目录下,此时在#include中要用双引号("")。
(4)特殊符号,预编译[bian yi][yu bian yi]程序[bian yi cheng xu][yu bian yi cheng xu]可以识别[shi bie]一些特殊的符号。
例如在源程序[yuan cheng xu]中出现的LINE标识[biao shi]将被解释[jie shi]为当前行号(十进制[shi jin zhi]数),FILE则被解释[jie shi]为当前被编译[bian yi]的C源程序[yuan cheng xu]的名称。预编译[bian yi][yu bian yi]程序[bian yi cheng xu][yu bian yi cheng xu]对于在源程序[yuan cheng xu]中出现的这些串将用合适的值进行替换[ti huan]。
预编译[bian yi][yu bian yi]程序[bian yi cheng xu][yu bian yi cheng xu]所完成的基本上是对源程序[yuan cheng xu]的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件[tiao jian]编译[bian yi]指令[zhi ling]、没有特殊符号的输出[shu chu]文件[wen jian]。这个文件[wen jian]的含义同没有经过预处理的源文件[wen jian]是相同的,但内容有所不同。下一步,此输出[shu chu]文件[wen jian]将作为编译[bian yi]程序[bian yi cheng xu]的输出[shu chu]而被翻译成为机器指令[zhi ling][ji qi zhi ling]。
第二个阶段编译[bian yi]、优化[you hua]阶段,经过预编译[bian yi][yu bian yi]得到的输出[shu chu]文件[wen jian]中,只有常量[chang liang];如数字[shu zi]、字符[zi fu]串[zi fu chuan]、变量[bian liang]的定义,以及C语言[yu yan]的关键字[guan jian zi],如main,if,else,for,while,{,}, +,-,*,/等等。
编译[bian yi]程序[bian yi cheng xu]所要作得工作就是通过词法[ci fa]分析[ci fa fen xi]和语法[yu fa]分析[yu fa fen xi],在确认[que ren]所有的指令[zhi ling]都符合语法[yu fa]规则之后,将其翻译成等价的中间代码[dai ma][zhong jian dai ma]表示或汇编[hui bian]代码[dai ma]。
优化[you hua]处理是编译[bian yi]系统[xi tong][bian yi xi tong]中一项比较艰深的技术。它涉及到的问题[wen ti]不仅同编译[bian yi]技术本身有关,而且同机器的硬件[ying jian]环境也有很大的关系。优化[you hua]一部分是对中间代码[dai ma][zhong jian dai ma]的优化[you hua]。这种优化[you hua]不依赖于具体的计算机。另一种优化[you hua]则主要针对目标[mu biao]代码[dai ma]的生成而进行的。
对于前一种优化[you hua],主要的工作是删除[shan chu]公共表达式[biao da shi]、循环[xun huan]优化[you hua](代码[dai ma]外提、强度削弱[qiang du xue ruo]、变换[bian huan]循环[xun huan]控制[kong zhi]条件[tiao jian]、已知量的合并等)、复写传播,以及无用赋值[fu zhi]的删除[shan chu],等等。
后一种类型[lei xing]的优化[you hua]同机器的硬件[ying jian]结构[jie gou]密切相关,最主要的是考虑是如何充分利用机器的各个硬件[ying jian]寄存器[ji cun qi]存放的有关变量[bian liang]的值,以减少对于内存[nei cun]的访问次数。另外,如何根据机器硬件[ying jian]执行[zhi hang]指令[zhi ling]的特点(如流水线[liu shui xian]、RISC、CISC、VLIW等)而对指令[zhi ling]进行一些调整[tiao zheng]使目标[mu biao]代码[dai ma]比较短,执行[zhi hang]的效率比较高,也是一个重要的研究课题。
汇编[hui bian]
汇编[hui bian]实际上指把汇编[hui bian]语言[yu yan][hui bian yu yan]代码[dai ma]翻译成目标[mu biao]机[mu biao ji]器指令[zhi ling][ji qi zhi ling]的过程。对于被翻译系统[xi tong]处理的每一个C语言[yu yan]源程序[yuan cheng xu],都将最终经过这一处理而得到相应的目标[mu biao]文件[wen jian]。目标[mu biao]文件[wen jian]中所存放的也就是与源程序[yuan cheng xu]等效的目标[mu biao]的机器语言[yu yan][ji qi yu yan]代码[dai ma]。目标[mu biao]文件[wen jian]由段组成。通常一个目标[mu biao]文件[wen jian]中至少有两个段:
代码[dai ma]段:该段中所包含的主要是程序的指令[zhi ling]。该段一般是可读和可执行[zhi hang]的,但一般却不可写。
数据[shu ju]段:主要存放程序中要用到的各种全局变量[bian liang][quan ju bian liang]或静态的数据[shu ju]。一般数据[shu ju]段都是可读,可写,可执行[zhi hang]的。
UNIX环境下主要有三种类型[lei xing]的目标[mu biao]文件[wen jian]:
(1)可重定位[ding wei]文件[wen jian]
其中包含有适合于其它目标[mu biao]文件[wen jian]链接[lian jie]来创建一个可执行[zhi hang]的或者共享的目标[mu biao]文件[wen jian]的代码[dai ma]和数据[shu ju]。
(2)共享的目标[mu biao]文件[wen jian]
这种文件[wen jian]存放了适合于在两种上下文[shang xia wen]里链接[lian jie]的代码[dai ma]和数据[shu ju]。第一种是链接[lian jie]程序可把它与其它可重定位[ding wei]文件[wen jian]及共享的目标[mu biao]文件[wen jian]一起处理来创建另一个 目标[mu biao]文件[wen jian];第二种是动态[dong tai]链接[lian jie]程序将它与另一个可执行[zhi hang]文件[wen jian][ke zhi hang wen jian]及其它的共享目标[mu biao]文件[wen jian]结合到一起,创建一个进程[jin cheng]映象。
(3)可执行[zhi hang]文件[wen jian][ke zhi hang wen jian]
它包含了一个可以被操作系统[xi tong][cao zuo xi tong]创建一个进程[jin cheng]来执行[zhi hang]之的文件[wen jian]。汇编[hui bian]程序[hui bian cheng xu]生成的实际上是第一种类型[lei xing]的目标[mu biao]文件[wen jian]。对于后两种还需要其他的一些处理方能得到,这个就是链接[lian jie]程序的工作了。
链接[lian jie]过程
由汇编[hui bian]程序[hui bian cheng xu]生成的目标[mu biao]文件[wen jian]并不能立即就被执行[zhi hang],其中可能还有许多没有解决的问题[wen ti]。
例如,某个源文件[wen jian]中的函数[han shu]可能引用[yin yong]了另一个源文件[wen jian]中定义的某个符号(如变量[bian liang]或者函数[han shu]调用[tiao yong][han shu tiao yong]等);在程序中可能调用[tiao yong]了某个库文件[wen jian]中的函数[han shu],等等。所有的这些问题[wen ti],都需要经链接[lian jie]程序的处理方能得以解决。
链接[lian jie]程序的主要工作就是将有关的目标[mu biao]文件[wen jian]彼此相连接[lian jie],也即将在一个文件[wen jian]中引用[yin yong]的符号同该符号在另外一个文件[wen jian]中的定义连接[lian jie]起来,使得所有的这些目标[mu biao]文件[wen jian]成为一个能够诶操作系统[xi tong][cao zuo xi tong]装入[zhuang ru]执行[zhi hang]的统一整体。
根据开发人员指定的同库函数[han shu]的链接[lian jie]方式的不同,链接[lian jie]处理可分为两种:
(1)静态链接[lian jie]
在这种链接[lian jie]方式下,函数[han shu]的代码[dai ma]将从其所在地静态链接[lian jie]库中被拷贝[kao bei]到最终的可执行[zhi hang]程序[zhi hang cheng xu][ke zhi hang cheng xu]中。这样该程序在被执行[zhi hang]时这些代码[dai ma]将被装入[zhuang ru]到该进程[jin cheng]的虚拟[xu ni]地址[di zhi][xu ni di zhi]空间[kong jian][di zhi kong jian]中。静态链接[lian jie]库实际上是一个目标[mu biao]文件[wen jian]的集合,其中的每个文件[wen jian]含有库中的一个或者一组相关函数[han shu]的代码[dai ma]。
(2) 动态[dong tai]链接[lian jie]
在此种方式下,函数[han shu]的代码[dai ma]被放到称作是动态[dong tai]链接[lian jie]库[dong tai lian jie ku]或共享对象[dui xiang]的某个目标[mu biao]文件[wen jian]中。链接[lian jie]程序此时所作的只是在最终的可执行[zhi hang]程序[zhi hang cheng xu][ke zhi hang cheng xu]中记录下共享对象[dui xiang]的名字以及其它少量的登记信息[xin xi]。在此可执行[zhi hang]文件[wen jian][ke zhi hang wen jian]被执行[zhi hang]时,动态[dong tai]链接[lian jie]库[dong tai lian jie ku]的全部内容将被映射[ying she]到运行[yun hang]时[yun hang shi]相应进程[jin cheng]的虚地址[di zhi]空间[kong jian][di zhi kong jian]。动态[dong tai]链接[lian jie]程序将根据可执行[zhi hang]程序[zhi hang cheng xu][ke zhi hang cheng xu]中记录的信息[xin xi]找到相应的函数[han shu]代码[dai ma]。
对于可执行[zhi hang]文件[wen jian][ke zhi hang wen jian]中的函数[han shu]调用[tiao yong][han shu tiao yong],可分别采用动态[dong tai]链接[lian jie]或静态链接[lian jie]的方法[fang fa]。使用动态[dong tai]链接[lian jie]能够使最终的可执行[zhi hang]文件[wen jian][ke zhi hang wen jian]比较短小,并且当共享对象[dui xiang]被多个进程[jin cheng]使用时能节约一些内存[nei cun],因为在内存[nei cun]中只需要保存一份此共享对象[dui xiang]的代码[dai ma]。但并不是使用动态[dong tai]链接[lian jie]就一定比使用静态链接[lian jie]要优越。在某些情况[qing kuang]下动态[dong tai]链接[lian jie]可能带来一些性能[xing neng]上损害。
我们在linux使用的gcc编译[bian yi]器[bian yi qi]便是把以上的几个过程进行捆绑,使用户[yong hu]只使用一次命令[ming ling]就把编译[bian yi]工作完成,这的确方便了编译[bian yi]工作,但对于初学者了解编译[bian yi]过程就很不利了,下图便是gcc代理的编译[bian yi]过程:
从上图可以看到:
预编译[bian yi][yu bian yi]
将.c 文件[wen jian]转化成 .i文件[wen jian]
使用的gcc命令[ming ling]是:gcc –E
对应于预处理命令[ming ling]cpp
编译[bian yi]
将.c/.h文件[wen jian]转换[zhuan huan]成.s文件[wen jian]
使用的gcc命令[ming ling]是:gcc –S
对应于编译[bian yi]命令[ming ling] cc –S
汇编[hui bian]
将.s 文件[wen jian]转化成 .o文件[wen jian]
使用的gcc 命令[ming ling]是:gcc –c
对应于汇编[hui bian]命令[ming ling]是 as
链接[lian jie]
将.o文件[wen jian]转化成可执行[zhi hang]程序[zhi hang cheng xu][ke zhi hang cheng xu]
使用的gcc 命令[ming ling]是: gcc
对应于链接[lian jie]命令[ming ling]是 ld
总结起来编译[bian yi]过程就上面的四个过程:预编译[bian yi][yu bian yi]、编译[bian yi]、汇编[hui bian]、链接[lian jie]。Lia了解这四个过程中所做的工作,对我们理解头文件[wen jian]、库等的工作过程是有帮助的,而且清楚的了解编译[bian yi]链接[lian jie]过程还对我们在编程[bian cheng]时定位[ding wei]错误[cuo wu],以及编程[bian cheng]时尽量调动编译[bian yi]器[bian yi qi]的检测错误[cuo wu]会有很大的帮助的。
- 0
- 顶一下