编译基础

因为下学期没有编译原理的课程(真的很坑爹),但是这部分内容真的很重要的,懂了这些遇到相关知识也比较容易理解。

我们在Windows下安装一个程序,一般需要一个后缀为exe或msi的可执行文件,双击之后,如果是第一次安装,会出现安装向导,一步一步指示你选择安装目录,选择安装哪些套件(在VS等IDE中),是否创建桌面快捷方式等等,这么下来,程序就在操作系统的注册表里注册了自己的信息(包括自己的元数据,即程序位置;配置信息等),这样当我们使用快捷键Win+S时,就可以搜索程序的名字来快速打开它,而且程序还可以关联文件后缀,比如我们安装了Dev C++ IDE时,我们希望只要是.c , .cpp文件都用它打开,就可以关联后缀。关联后缀其实是让操作系统知道如何找到打开这种文件的默认程序(操作系统是知道默认程序的路径的)。

在linux命令行下,安装了一个程序,比如安装了vim,这时就可以直接用vim XX.txt打开某个文件,因为bash知道vim对应的二进制代码(机器代码)放在什么位置,可以直接调用它们来处理文本等。

下面我再从开发者的角度解释编译过程:

首先我们需要配置文件(configure脚本可以检测到不同的系统环境,执行这个脚本可以生成适合各个环境的Makefile),好让编译器知道标准库文件的位置、应该安装的位置、要安装哪些功能(模块)。你可以直接执行configure脚本,也可以传递给它一些参数以指定自定义安装目录、是否安装gdb调试模块(安装bochs模拟器时)等其他配置信息。(详见阮叔博客

compiler

这样就生成了Makefile,存放了各种依赖关系,告诉make命令如何编译和链接这些.c和.h文件。make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。这就是解决依赖。

预编译

工程中源文件需要使用某个或某些头文件,在第一次的时候编译这些头文件,后续在需要的时候直接用就行,这样节省了时间,提高编译效率。另外,调试时可以声明宏来指明是否编译这个头文件。

预处理:

去除头文件,执行宏替换,去掉注释。

编译

编译器首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,编译器把代码翻译成汇编语言。汇编语言是非常有用的,它为不同高级语言不同编译器提供了通用的语言。如:C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。

汇编:

将编译生成的汇编代码转为目标文件,很多时候这个文件并不能直接执行,因为还需要一些额外的库函数,比如标准库,如果你opencv程序,还需要第三方的opencv的库函数,把这些库函数包括进可执行文件,就是下述的链接过程。

链接:

分为两种,静态链接是把需要的外部函数全部加载进可执行文件,动态链接是在程序运行时如果需要每个外部库,就加载进来,这么做的好处就是打包的程序文件较小,但是分发给其他用户使用时,他们必须安装对应的外部库。前者的程序文件较大,但是通用性强,后者文件较小,而且可以在内存中与其他进程分享这些外部库,但是需要其他用户额外安装这些库,比较麻烦。

安装:

经过链接,编译器在内存中生成了可执行文件,我们需要后续使用它,就需要保存到非易失的硬盘中,也就是相应安装目录了。

通知操作系统:

安装完成后,我们希望操作系统把它加入到一个索引表,便于我们搜索时快捷的打开程序,这就是开头提到的。在linux下,新安装的程序一般在/usr/bin下,输入新安装的程序,就会在这个路径下搜索并执行。

在我新安装的FreeBSD上执行 echo $PATH ,输出:

/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/home/freebsd/bin
这就是系统搜索命令的路径,而且一般是按照顺序搜索,如果系统命令与用户命令重名,可以指定完整路径解决。

至此编译和安装完成!

画外音:听说编译器是世界上第二复杂的东西,第一是操作系统,可我们数据库老师说database才是第二复杂的~~