Before the Running of C/C++ Programs

C/C++程序在运行之前做了大量的工作:预处理,编译,链接等等,理解这些,对程序更好的执行非常有帮助。

计算机只能识别机器指令,也就是机器码(Machine code)。汇编语言是一系列机器码的简单集合。高级语言可以方便程序员来快速编写程序,但是最终也需要转换为汇编语言或机器码。将高级语言转换为机器码的转换器叫做解释器(Interpreter)和编译器(compiler)    编译器将源码单元直接转换为汇编语言或机器码。

解释器(Interpreter),又叫做直译器,对源代码翻译一行就立马执行一行。BASIC、Fortran、Matlab等语言均是采用解释器来执行程序。

编译器则不同,编译器将源码直接转换为机器码或汇编语言。最终生成的目标文件是一个或多个由机器码组成的文件。这些文件运行速度快,运行所需的内存小。C、C++编译器还有一个另外的功能,就是单独编译(Separate Compilation)。单独编译的文件最终由链接器整合为可执行程序。单独编译的好处主要在于方便程序的模块化,库文件就是经测试可执行的目标文件集合。单独编译的另外一个好处就是避免产生把所有的源码载入内存超过编译器的最大限制或机器的内存大小的问题。如果可以,直接将源码全部载入内存可以提高编译速度。

编译过程

C/C++在编译之前会用预处理器(preprocessor)对源码进行处理。预处理器的主要工作就是将程序中的用预处理指令(preprocessor directives)定义的模式进行替换。预处理指令的主要作用是减少代码书写量,增加程序可读性。预处理指令在C/C++中广泛使用,主要包括1.宏定义2.文件包含3.条件编译等,此外,还有此外,还有布局控制:#pragma。在C++中其实是不鼓励使用预处理器的,例如,C++中用inline函数完成了宏替换。

编译器(complier)分两步工作,第一步解析预处理完成的代码:编译器主要将代码转换为小的代码单元,然后将他们用数据结构:树来组织起来。例如A+B,A和B是解析树的叶子。在这个时期,也对代码进行静态类型检查,之所以叫静态类型检查,是因为类型检查发生在编译器而不是程序的运行期。Java语言进行动态类型检查,这让Java更加强大,但也增加了程序运行期的负担。

在第一步和第二步之间,如果需要的话,全局优化器会对第一步生成的代码进行优化,以产生更小,更快的代码。

在第二步的时候,代码生成器会遍历解析树来产生对应的汇编语言(如果产生的是汇编语言,汇编处理器会继续处理)或机器码。最终的结果是产生一个以.o或者.obj结尾的文件。

链接器(linker)将一系列的目标文件组合为可被操作系统载入执行的可执行程序。如果某个目标文件在一个模块中引用了另外一个模块中的函数或变量,链接器将会验证这些函数或变量确实存在:通过搜索模块或模块的集合—库文件(library)。同时,链接器也会生成一个模块,用以在程序启动的时候发挥作用。

C/C++便于模块化开发主要是因为支持单独编译。单独编译带来好处的同时也存在问题:例如A文件中的函数需要访问B文件中的函数或变量,这时A文件需要对B文件的函数或变量进行声明,否则链接器就无法正常工作。

声明就是告诉编译器将要用到的函数或变量的名字以及它们的类型。定义就是创建一个变量或函数(分配空间),对于变量,为它分配存储数据的空间;对于函数,编译器将为其生成机器码,最终也占用了空间。

头文件是一个包含库中所有函数或变量声明的文件。采用#include方式,就可以声明库中所有的函数或变量了。#include<stdio.h>,C风格包含,等同于#include<cstdio>。在C/C++中使用库文件的方法:1.包含头文件 2.使用函数或变量3.将库文件链接到可执行程序中。

当在C、C++中引用外部函数或变量时需要声明,链接器在碰到这些引用时,采用下面的两种方式中的其一进行处理:如果没有碰到它们的定义,它会将其加入“unresolved references”列表中;如果已经碰到它们的定义,则该引用已经解决。如果链接器在本地所有目标模块中都没有找到定义,它会到库文件中进行查找,库文件一般有索引,链接器因而不用遍历库中的所有object模块,它在索引中进行查找。当链接器找到函数定义时,整个包含该函数的目标模块将会链接到可执行程序中。注意:不是仅仅链接该函数定义,也不是将整个库都链接进去(否则可执行程序会相当大),这也提醒我们,如果生成库时,我们采用一个function定义来形成一个object module,我们可以让使用该库的程序体积最小。

由于链接器搜索时采用固定顺序,我们可以阻止程序使用库中的函数,只需自己定义一个和库函数同名的文件,由于链接器先于库文件搜索到你的文件,因而程序总是使用的你的函数。这可能是一个bug, C++中采用namespace预防了这一个问题。


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。