Pages

mercredi 18 août 2010

C,C++编译过程详解

C、C++语言语言编译过程总结详解

     http://blog.csdn.net/pizi0475/archive/2010/06/17/5676440.aspx


偶然看到一位网友的文章写得很全面,故此稍加整理,以作备忘。
    C语言的编译链接过程要把我们编写的一个c程序(源代码转换成可以在硬件上运行的程序(可执行代码[),需要进行编译和链接。
编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。
链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:
C语言[yu yan]的编译[bian yi]链接[lian jie]过程
编译过程
编译过程又可以分成两个阶段:编译和汇编
编译
编译是读取源程序字符流,对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:
    第一个阶段是预处理阶段,在正式的编译阶段之前进行。
预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。
#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。
主要是以下几方面的处理:
1)宏定义指令,如 #define a  b
    对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量[的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
    这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

3) 头文件包含指令,如#include "FileName"或者#include <FileName>等。
    在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出中,以供编译程序对之进行处理。
包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。
 
    第二个阶段编译[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]过程:

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]会有很大的帮助的。

Aucun commentaire:

Enregistrer un commentaire