编译c源码-解读C/C++从源代码到可执行程序的过程

编译,编译器读取源程序(字符流),分析词法和句型,将中间语言指令转换为功能等价的汇编代码,然后由汇编器转换为机器语言,但要根据操作系统的可执行格式需要链接来生成可执行程序

代码-->预处理-->编译-->优化-->汇编-->链接-->可执行文件

源–(编译)–>汇编–(汇编)–>Obj–(链接)–>PE/ELF

1.编译预处理

读取c源程序,处理其中的伪指令(#开头的指令)和特殊符号

【分析】伪指令主要包括以下四个方面

编译c源码-解读C/C++从源代码到可执行程序的过程

(1)宏定义指令,如#defineNameTokenString、#undef等。对于前面的指令,预编译要做的就是将程序中的所有Name都替换为TokenString,但作为字符串常量的Name不会被替换。 对于前者,某个宏的定义会被取消,这样后续出现的字符串就不会被替换。

(2)条件编译指令,如#ifdef、#ifndef、#else、#elif、#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译器处理哪些代码。预编译器会根据相关文件过滤掉这些必要的代码

(3)头文件中包含指令,如#include "FileName"或#include等。头文件中通常用伪指令#define定义大量宏(最常见的字符常量),同时包含各种外部符号的声明。 使用头文件的主要目的是使各个定义可用于多个不同的 C 源程序。 因为在需要使用这个定义的C源程序中,只需要添加一个#include语句即可,不需要在这个文件上重复这个定义。 预编译器会将头文件中的所有定义添加到它形成的输出文件中,供编译器处理。

c源程序中包含的头文件可以由系统提供,这样的头文件通常放在/usr/include目录下。 #将它们包含在程序中以使用尖括号()。 另外,开发人员还可以定义自己的头文件,这些头文件通常放在与c源程序同一目录下。 这时,#include 中应该使用双冒号(“”)。

(4)特殊符号,预编译器可以识别一些特殊符号。 例如,源程序中出现的 LINE 标记将被解释为当前行号(十的补码),FILE 将被解释为当前编译的 C 源程序的名称。 预编译器将用适当的值替换源程序中出现的那些字符串。

预编译器所做的基本上就是对源程序的“替换”。 进行此替换后,将生成一个没有宏定义、没有条件编译指令、也没有特殊符号的输出文件。 该文件的含义与未经预处理的源文件相同,但内容不同。 在下一步中,该输出文件被翻译成机器指令作为编译器的输出。

2、编译阶段

预编译得到的输出文件中,只会有常量。 如C语言的数字、字符串、变量、关键字的定义,如main、if、else、for、while、{、}、+、-、*、等。 预编译器需要做的是分析语法和句型,并在确认所有指令符合句型规则后,将其翻译为等效的中间代码表示或汇编代码。

3.优化阶段

优化处理是编译系统中一项相对不起眼​​的技术。 它涉及到的问题不仅与编译技术本身有关,而且与机器的硬件环境有很大关系。 优化的一部分是中间代码的优化。 这些优化不依赖于特定的计算机。 另一种优化主要针对目标代码的生成。 上图中,我们把优化阶段放在编译后的程序旁边,这是一个比较广泛的表示。

对于前者的优化,主要工作是删除公共表达式、循环优化(代码提取、强度弱化、改变循环控制条件、已知量的合并等)、复制传播、删除无用的形参等。

后一类优化与机器的硬件结构密切相关。 最重要的考虑是如何充分利用机器各个硬件寄存器中存储的相关变量的值来减少对显存的访问次数。 另外,如何根据机器硬件(如流水线、RISC、CISC、VLIW等)执行的指令的特点对指令进行一些调整,使目标代码更短、执行效率更高也是一个问题。一个重要的研究课题。

优化后的汇编代码必须经过汇编器编译并转换为相应的机器指令才能被机器执行。

4. 组装过程

汇编过程实际上是指将汇编语言代码翻译成目标机器指令的过程。 对于翻译系统处理后的每个C语言源程序,经过处理后都会得到相应的目标文件。 目标文件中存储的是相当于源程序的目标的机器语言代码。

目标文件由多个部分组成。 一般来说,目标文件中至少有两个部分:

代码部分 该部分主要包含程序指令。 该部分通常可读且可执行,但通常不可写。

数据段主要存放程序中要用到的各种全局变量或静态数据。 通常数据段是可读、可写和可执行的。

UNIX 环境中的目标文件主要分为三种类型:

(1) 可重定位文件包含适合与其他目标文件链接以创建可执行或共享目标文件的代码和数据。

(2) 共享目标文件 这些文件存储适合在两个上下文中链接的代码和数据。 首先是链接器可以将其与其他可重定位文件和共享目标文件一起处理以创建另一个目标文件; 第二是动态链接器可以将其与另一个可执行文件和其他共享对象文件组合起来创建进程映像。

(3)可执行文件它包含可由操作系统创建并执行的文件。

汇编器生成的实际上是第一种类型的目标文件。 对于后两者编译c源码,需要进行一些其他处理才能获得它们。 这是链接器的工作。

5. 链接器

汇编器生成的目标文件并不能立即执行,可能还有很多未解决的问题。 例如,一个源文件中的函数可能引用另一个源文件中定义的符号(例如变量或函数调用等); 库文件中的函数可以在程序中调用,等等。 所有此类问题只能通过处理链接器来解决。

链接器的主要工作是将相关的目标文件相互链接起来,并将一个文件中引用的符号与另一个文件中该符号的定义正式链接起来,使所有这些目标文件成为一个可以放置的文件由操作系统。 成为一个统一的整体。

根据开发者指定的同一库函数的链接形式不同,链接处理可以分为两种:

(1) 静态链接 在这些链接形式中,函数的代码将从其所在的静态链接库复制到最终的可执行程序中。 当程序执行时,此类代码将被放入进程的虚拟地址空间中。 静态链接库实际上是目标文件的集合,每个目标文件都包含库中一个或一组相关函数的代码。 (个人说明:静态链接将链接库的代码复制到可执行程序中,导致可执行程序体积较大)

(2) 动态链接 在这种形式中,函数的代码被放置在称为动态链接库或共享对象的目标文件中。 此时链接器所做的就是在最终的可执行文件中记录共享对象的名称和少量注册信息。 当这个可执行文件被执行时,动态链接库的全部内容都会在运行时被映射到对应进程的虚拟地址空间。 动态链接器会根据可执行程序中记录的信息找到相应的功能代码。 (个人说明:动态链接是指将需要链接的代码放在一个共享对象中,将共享对象映射到进程虚拟地址空间,链接程序记录了可执行程序将来需要的代码信息,并快速定位到对应的代码片段。)

对于可执行文件中的函数调用,可以分别使用动态链接或静态链接。 使用动态链接可以使最终的可执行文件更短编译c源码,并且当共享对象被多个进程使用时可以节省一些显存,因为共享对象的代码只需要在显存中保存一份。 但并不是说使用动态链接就一定优于使用静态链接。 在个别情况下,动态链接可能会导致一些性能损失。

经过以上五个过程,C源程序最终转换为可执行文件。 默认情况下,可执行文件名为 a.out。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 源码编译 编译c源码-解读C/C++从源代码到可执行程序的过程 https://www.wkzy.net/game/145841.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务