C语⾔⽂件的编译到执⾏的四个阶段
C语⾔⽂件的编译与执⾏的四个阶段并分别描述
C语⾔的编译链接过程要把我们编写的⼀个c程序(源代码)转换成可以在硬件上运⾏的程序(可执⾏代码),需要进⾏编译和链接。编译就是把⽂本形式源代码翻译为机器语⾔形式的⽬标⽂件的过程。链接是把⽬标⽂件、操作系统的启动代码和⽤到的库⽂件进⾏组织形成最终⽣成可执⾏代码的过程。
第⼀个阶段:
是预处理阶段,在正式的编译阶段之前进⾏。预处理阶段将根据已放置在⽂件中的预处理指令来修改源⽂件的内容。
如#include指令就是⼀个预处理指令,它把头⽂件的内容添加到.cpp⽂件中。这个在编译之前修改源⽂件的⽅式提供了很⼤的灵活性,以适应不同的计算机和操作系统环境的限制。⼀个环境需要的代码跟另⼀个环境所需的代码可能有所不同,因为可⽤的硬件或操作系统是不同的。在许多情况下,可以把⽤于不同环境的代码放在同⼀个⽂件中,再在预处理阶段修改代码,使之适应当前的环境。
主要是以下⼏⽅⾯的处理:
(1)宏定义指令,如 #define a b
对于这种伪指令,预编译所要做的是将程序中的所有a⽤b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
这些伪指令的引⼊使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进⾏处理。预编译程序将根据有关的⽂件,将那些不必要的代码过滤掉。
(3) 头⽂件包含指令,如#include "FileName"或者#include 等。
在头⽂件中⼀般⽤伪指令#define定义了⼤量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采⽤头⽂件的⽬的主要是为了使某些定义可以供多个不同的C源程序使⽤。因为在需要⽤到这些定义的C源程序中,只需加上⼀条#include语句即可,⽽不必再在此⽂件中将这些定义重复⼀遍。预编译程序将把头⽂件中的定义统统都加⼊到它所产⽣的输出⽂件中,以供编译程序对之进⾏处理。包含到c源程序中的头⽂件可以是系统提供的,这些头⽂件⼀般被放在 /usr/include⽬录下。在程序中#include它们要使⽤尖括号(< >)。另外开发⼈员也可以定义⾃⼰的头⽂件,这些⽂件⼀般与c源程序放在同⼀⽬录下,此时在#include中要⽤双引号("")。
(4)特殊符号,预编译程序可以识别⼀些特殊的符号。
例如在源程序中出现的LINE标识将被解释为当前⾏号(⼗进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将⽤合适的值进⾏替换。
预编译程序所完成的基本上是对源程序的“替代”⼯作。经过此种替代,⽣成⼀个没有宏定义、没有条件编译指令、没有特殊符号的输出⽂件。这个⽂件的含义同没有经过预处理的源⽂件是相同的,但内容有所不同。下⼀步,此输出⽂件将作为编译程序的输出⽽被翻译成为机器指令。
第⼆个阶段:
编译、优化阶段,经过预编译得到的输出⽂件中,只有常量;如数字、字符串、变量的定义,以及C语⾔的关键字,
如main,if,else,for,while,{,}, +,-,*,\等等。
编译程序所要作得⼯作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表⽰或汇编代码。
优化处理是编译系统中⼀项⽐较艰深的技术。它涉及到的问题不仅同编译技术本⾝有关,⽽且同机器的硬件环境也有很⼤的关系。优化⼀部分是对中间代码的优化。这种优化不依赖于具体的计算机。另⼀种优化则主要针对⽬标代码的⽣成⽽进⾏的。
对于前⼀种优化,主要的⼯作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及⽆⽤赋值的删除,等等。
后⼀种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利⽤机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执⾏指令的特点(如流⽔线、RISC、CISC、VLIW等)⽽对指令进⾏⼀些调整使⽬标代码⽐较短,执⾏的效率⽐较⾼,也是⼀个重要的研究课题。
第三阶段:
汇编
汇编实际上指把汇编语⾔代码翻译成⽬标机器指令的过程。对于被翻译系统处理的每⼀个C语⾔源程序,都将最终经过这⼀处理⽽得到相应的⽬标⽂件。⽬标⽂件中所存放的也就是与源程序等效的⽬标的机器语⾔代码。⽬标⽂件由段组成。通常⼀个⽬标⽂件中⾄少有两个段:
代码段:该段中所包含的主要是程序的指令。该段⼀般是可读和可执⾏的,但⼀般却不可写。
数据段:主要存放程序中要⽤到的各种全局变量或静态的数据。⼀般数据段都是可读,可写,可执⾏的。
UNIX环境下主要有三种类型的⽬标⽂件:
(1)可重定位⽂件
其中包含有适合于其它⽬标⽂件链接来创建⼀个可执⾏的或者共享的⽬标⽂件的代码和数据。
(2)共享的⽬标⽂件
这种⽂件存放了适合于在两种上下⽂⾥链接的代码和数据。第⼀种是链接程序可把它与其它可重定位⽂件及共享的⽬标⽂件⼀起处理来创建另⼀个⽬标⽂件;第⼆种是动态链接程序将它与另⼀个可执⾏⽂件及其它的共享⽬标⽂件结合到⼀起,创建⼀个进程映象。
(3)可执⾏⽂件
它包含了⼀个可以被操作系统创建⼀个进程来执⾏之的⽂件。汇编程序⽣成的实际上是第⼀种类型的⽬标⽂件。对于后两种还需要其他的⼀些处理⽅能得到,这个就是链接程序的⼯作了。
第四阶段:
链接过程
由汇编程序⽣成的⽬标⽂件并不能⽴即就被执⾏,其中可能还有许多没有解决的问题。
例如,某个源⽂件中的函数可能引⽤了另⼀个源⽂件中定义的某个符号(如变量或者函数调⽤等);在程序中可能调⽤了某个库⽂件中的函数,等等。所有的这些问题,都需要经链接程序的处理⽅能得以解决。
链接程序的主要⼯作就是将有关的⽬标⽂件彼此相连接,也即将在⼀个⽂件中引⽤的符号同该符号在另外⼀个⽂件中的定义连接起来,使得所有的这些⽬标⽂件成为⼀个能够诶操作系统装⼊执⾏的统⼀整体。
根据开发⼈员指定的同库函数的链接⽅式的不同,链接处理可分为两种:linux下gcc编译的四个步骤
(1)静态链接
在这种链接⽅式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执⾏程序中。这样该程序在被执⾏时这些代码将被装⼊到该进程的虚拟地址空间中。静态链接库实际上是⼀个⽬标⽂件的集合,其中的每个⽂件含有库中的⼀个或者⼀组相关函数的代码。
(2) 动态链接
在此种⽅式下,函数的代码被放到称作是动态链接库或共享对象的某个⽬标⽂件中。链接程序此时所作的只是在最终的可执⾏程序中记录下共享对象的名字以及其它少量的登记信息。在此可执⾏⽂件被执⾏时,动态链接库的全部内容将被映射到运⾏时相应进程的虚地址空间。动态链接程序将根据可执⾏程序中记录的信息到相应的函数代码。
对于可执⾏⽂件中的函数调⽤,可分别采⽤动态链接或静态链接的⽅法。使⽤动态链接能够使最终的可执⾏⽂件⽐较短⼩,并且当共享对象被多个进程使⽤时能节约⼀些内存,因为在内存中只需要保存⼀份此共享对象的代码。但并不是使⽤动态链接就⼀定⽐使⽤静态链接要优越。在某些情况下动态链接可能带来⼀些性能上损害。
我们在linux使⽤的gcc编译器便是把以上的⼏个过程进⾏捆绑,使⽤户只使⽤⼀次命令就把编译⼯作完成,这的确⽅便了编译⼯作,但对于初学者了解编译过程就很不利了,下图便是gcc代理的编译过程:
从上图可以看到:
预编译
将.c ⽂件转化成 .i⽂件
使⽤的gcc命令是:gcc –E
对应于预处理命令cpp
编译
将.c/.h⽂件转换成.s⽂件
使⽤的gcc命令是:gcc –S
对应于编译命令 cc –S
汇编
将.s ⽂件转化成 .o⽂件
使⽤的gcc 命令是:gcc –c
对应于汇编命令是 as
链接
将.o⽂件转化成可执⾏程序
使⽤的gcc 命令是: gcc
对应于链接命令是 ld
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论