编译器的工作过程

目录

  • 为什么需要编译?
  • 第一步 编写配置(configure)文件
  • 为什么需要 configure ?
  • 如何配置?
  • 第二步 确定标准库和头文件的位置
  • 第三步 确定依赖关系
  • 为什么需要确定依赖关系?
  • 为什么先有 configure 脚本,再有 makefile 文件?
  • 第四步 头文件的预编译(Precompilation)
  • 为什么需要进行头文件的预编译?
  • 是不是所有的头文件内容都会被预编译的?
  • 第五步 预处理(Preprocessing)
  • 第六步 编译(Compilation)
  • 这种转码后的文件,称为什么?
  • 第七步 连接(Linking)
  • 静态链接是什么?
  • 动态链接是什么?
  • make 命令的作用是从哪一步到哪一步?
  • 第八步 安装(Installation)
  • 第九步 操作系统连接
  • make install 命令是干啥子用的?

为什么需要编译?

源码要运行,必须先转成二进制的机器码。

这是编译器的任务。

第一步 编写配置(configure)文件

为什么需要 configure ?

编译器在开始工作之前,需要知道当前的系统环境,比如标准库在哪里、软件的安装位置在哪里、需要安装哪些组件等等。

这是因为不同计算机的系统环境不一样,通过指定编译参数,编译器就可以灵活适应环境,编译出各种环境都能运行的机器码。

这个确定编译参数的步骤,就叫做"配置"(configure)。

如何配置?

这些配置信息保存在一个配置文件之中,约定俗成是一个叫做configure的脚本文件。

通常它是由autoconf工具生成的。

编译器通过运行这个脚本,获知编译参数。

configure脚本已经尽量考虑到不同系统的差异,并且对各种编译参数给出了默认值。

如果用户的系统环境比较特别,或者有一些特定的需求,就需要手动向configure脚本提供编译参数。

例如:

./configure --prefix=/www --with-mysql

上面的代码的意思是,用户指定安装后的文件保存在www目录,并且编译时加入mysql模块的支持。

第二步 确定标准库和头文件的位置

源码肯定会用到标准库函数(standard library)和头文件(header)。

它们可以存放在系统的任意目录中,编译器实际上没办法自动检测它们的位置,只有通过配置文件才能知道。

编辑好 configure 文件后,编译的第二步,就是从配置文件中知道标准库和头文件的位置。

一般来说,配置文件会给出一个清单,列出几个具体的目录。

等到编译时,编译器就按顺序到这几个目录中,寻找目标。

第三步 确定依赖关系

为什么需要确定依赖关系?

对于大型项目来说,源码文件之间往往存在依赖关系,编译器需要确定编译的先后顺序。

假定A文件依赖于B文件,编译器应该保证做到下面两点。

(1)只有在B文件编译完成后,才开始编译A文件。
(2)当B文件发生变化时,A文件会被重新编译。

编译顺序保存在一个叫做makefile的文件中,里面列出哪个文件先编译,哪个文件后编译。

为什么先有 configure 脚本,再有 makefile 文件?

而makefile文件由configure脚本运行生成,这就是为什么编译时configure必须首先运行的原因。

在确定依赖关系的同时,编译器也确定了,编译时会用到哪些头文件。

第四步 头文件的预编译(Precompilation)

为什么需要进行头文件的预编译?

不同的源码文件,可能引用同一个头文件(比如stdio.h)。

编译的时候,头文件也必须一起编译。为了节省时间,编译器会在编译源码之前,先编译头文件。

这保证了头文件只需编译一次,不必每次用到的时候,都重新编译了。

是不是所有的头文件内容都会被预编译的?

不过,并不是头文件的所有内容,都会被预编译。用来声明宏的#define命令,就不会被预编译。

第五步 预处理(Preprocessing)

预编译完成后,编译器就开始替换掉源码中bash的头文件和宏。

编译器在这一步还会移除注释。

这一步称为"预处理"(Preprocessing),因为完成之后,就要开始真正的处理了。

第六步 编译(Compilation)

预处理之后,编译器就开始生成机器码。

对于某些编译器来说,还存在一个中间步骤,会先把源码转为汇编码(assembly),然后再把汇编码转为机器码。

这种转码后的文件,称为什么?

这种转码后的文件称为对象文件(object file)。

第七步 连接(Linking)

对象文件还不能运行,必须进一步转成可执行文件,因为还有一些外部的函数库代码没有集成进去。

静态链接是什么?

编译器的下一步工作,就是把外部函数的代码(通常是后缀名为.lib和.a的文件),添加到可执行文件中。这就叫做连接(linking)。这种通过拷贝,将外部函数库添加到可执行文件的方式,叫做静态连接(static linking)

动态链接是什么?

其实就是共享库的应用。

make 命令的作用是从哪一步到哪一步?

make命令的作用,就是从第四步头文件预编译开始,一直到第七步连接。

第八步 安装(Installation)

上一步的连接是在内存中进行的,即编译器在内存中生成了可执行文件。

下一步,必须将可执行文件保存到用户事先指定的安装目录。

表面上,这一步很简单,就是将可执行文件(连带相关的数据文件)拷贝过去就行了。

但是实际上,这一步还必须完成创建目录、保存文件、设置权限等步骤。

这整个的保存过程就称为"安装"(Installation)。

第九步 操作系统连接

可执行文件安装后,必须以某种方式通知操作系统,让其知道可以使用这个程序了。

比如,我们安装了一个文本阅读程序,往往希望双击txt文件,该程序就会自动运行。

这就要求在操作系统中,登记这个程序的元数据:文件名、文件描述、关联后缀名等等。

Linux系统中,这些信息通常保存在/usr/share/applications目录下的.desktop文件中。

另外,在Windows操作系统中,还需要在Start启动菜单中,建立一个快捷方式。

这些事情就叫做"操作系统连接"。

make install 命令是干啥子用的?

make install命令,就用来完成第八步"安装"和第九步"操作系统连接"这两步。

第十步 生成安装包

写到这里,源码编译的整个过程就基本完成了。

但是只有很少一部分用户,愿意耐着性子,从头到尾做一遍这个过程。

事实上,如果你只有源码可以交给用户,他们会认定你是一个不友好的家伙。

大部分用户要的是一个二进制的可执行程序,立刻就能运行。这就要求开发者,将上一步生成的可执行文件,做成可以分发的安装包。

所以,编译器还必须有生成安装包的功能。

通常是将可执行文件(连带相关的数据文件),以某种目录结构,保存成压缩文件包,交给用户。

——

本文整理自阮一峰的博客

原文地址:https://www.cnblogs.com/0x1D/p/4712706.html