一:C/C++可执行文件产生过程

441 阅读6分钟

一:C/C++可执行文件产生过程

生成一个C++程序共有三个步骤:

①预处理:代码在预处理器中运行,预处理器会识别代码中的元信息。

②编译:代码被编译或转换为计算机可以识别的目标文件。

③链接:独立的目标文件链接在一起变成一个应用程序。

代码的编译过程链:

源代码->预处理->编译(翻译过程)->汇编->目标文件->链接->可执行程序

--《摘自C指针》,其中”翻译过程“在原文中并没有提到,是我加上去的

预处理

首先程序会被送交给预处理器。预处理器执行以#开头的命令(指令)。

#include<stdio.h>
#include<string.h>
#include “xxx.h”
#define Max_COLS 20

以上的代码就称为预处理指令,由预处理器解释。

例如#include<iostream.h>,#include指令告诉预处理器:提取头文件中的所有内容并提供给当前文件。

主要处理规则如下: (摘自程序员的自我修养)

①将所有的“#define”删除,并且展开所有的宏定义

②处理所有条件预编译指令,比如“#if”,“#ifdef”,”#edlif”,“#else”,“endif”

③处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。

④删除所有的注释内容。

⑤添加行号和文件名标识,比如#2“hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。

⑥保留所有的#pragma编译器指令,因为编译器须要使用它们。

翻译过程(包含编译和解释)

把源代码转化成计算机能执行的形式(机器指令)。

翻译器分为:解释器和编译器

解释器
解释器将源代码转化成一些动作(它可由多组机器指令组成)并立即执行这些动作。诸如Python语言的解释器,先把整个程序转化成某种中间语言,然后由执行速度更快的解释器来执行。

优点:

  • 从写代码到执行代码的转换几乎能立即完成。

  • 源代码一旦出现错误,解释器能很容易地指出。

  • 较好的交互性和适于快速程序开发

做大项目时有些局限性:

  • 解释器必须驻留内存以执行程序。(执行会变慢)

  • 大部分的解释器要求一次输入整个源代码,造成内存空间的限制。

编译器

修改后的程序现在可以进入编译器了。编译器会把程序翻译成机器指令(即目标代码)。编译器直接把源代码转化成汇编语言或机器指令,结果会是生成一个或多个机器代码的文件。

优点:

编译器生成的程序往往只需较少的运行空间,并且执行速度更快。

分段编译

C语言可以分别编译各段程序,然后使用连接器把各段程序连接成一个完成的可执行程序。

优点:

当创建和测试程序的一部分能够正常运行后,就把它作为程序组块保存起来,并把它们一起收集加入库中,供其他程序员使用,以此隐藏各段程序的复杂性。

解释器和编译器的关系

解释器从写代码到执行代码的能立即完成。例如程序调试过程中,我们可以修改源代码则立即看到程序被修改的效果。而编译器是先直接把源代码转化成汇编语言或机器指令,但这段过程还不能看到运行一个程序的效果。使用编译器时,从写源代码转到执行代码,是一个较长的过程。

现代编译器

为了提高编译速度,一些编译器采用了内存中编译。大多数编译器,编译时每一步都要读写文件。内存中编译器几乎能和解释器一样响应。

编译一般分为两遍进行:

①第一遍

首先对预处理过的代码进行语法分析。编译器把源代码分解成小的单元并把它们按树形结构组织起来。表达式”A+B”中的”A”,”+”和”B”就是语法分析树的叶子节点。

②第一遍和第二遍之间(看具体情况而决定是否有这过程)

有时候会在编译的第一遍和第二遍之间使用全局优化器来生成更短,更快的代码。

③第二遍

由代码生成器遍历语法分析树,把树的每个节点转化成汇编语言或机器代码。如果代码生成器生成的是汇编语言,那么还必须用汇编器对其汇编。不管代码生成器生成的是汇编语言,还是直接生成的是机器代码,这两种情况的最终结果都是生成目标模块。有时也会在第二遍中使用窥孔优化器从相邻一段代码中查找冗余汇编语句。

类型检查

类型检查是编译器在第一遍中完成的。类型检查是检查函数参数是否正确使用,以防止许多程序设计错误。由于不是在程序运行阶段进行的,所以称为静态类型检查。C++默认不采用任何特殊的运行时支持来处理错误操作,但是我们可以通过编写一些代码以更换为动态类型检查。

静态类型检查的优点:

静态类型检查在编译时就告知程序员一些类型被误用,从而加快了执行时的速度。

汇编

汇编器是汇编代码转变成机器可以执行的指令

目标文件

链接

链接器把由编译器产生的目标代码和所需的其他附加代码(包括库函数)整合在一起,最终产生为完全可执行的程序。就是说链接器把一组目标模块连接成为一个可执行程序,操作系统可以装载和运行它。链接器能搜索称为“库”的特殊文件来处理它的所有引用。库将一组目标模块包含在一个文件中。库由一个被称为库管理器的程序来创建和维护。

链接器如何查找库

①如果还未遇到过某个函数或变量的定义,连接器会把它的标识符加到“未解析的引用”列表中。

②如果连接器遇到过函数或变量定义,那么这就是已解决的引用。(例如引用不到动态链接库,则就会报出“对XX未解析的引用”错误的信息)

③如果连接器在目标模块列表中不能找到函数或变量的定义,它将去查找库。