编译器的工作过程

来源:互联网 发布:淘宝怎样追加二次评论 编辑:程序博客网 时间:2024/06/03 20:10

本文主要针对gcc编译器,也就是针对C和C++,不一定适用于其他语言的编译。
原作者

编译器的任务

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

#include <stdio.h>int main(void){  fputs("Hello, world!\n", stdout);  return 0;}

编译过程

第一步 配置(configure)

确定编译参数的步骤,就叫做”配置”(configure)。
为什么确定编译参数?
编译器在开始工作之前,需要知道当前的系统环境

标准库在哪里软件的安装位置在哪里需要安装哪些组件

怎么配置
配置信息保存在一个配置文件之中,叫做configure的脚本文件,由autoconf工具生成的。编译器通过运行这个脚本,获知编译参数。

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

源码肯定会用到标准库函数(standard library)和头文件(header),存放在系统的任意目录
编译器从配置文件中知道标准库和头文件的位置

第三步 确定依赖关系

源码文件之间往往存在依赖关系,编译器需要确定编译的先后顺序
编译顺序保存在一个叫做makefile的文件中
makefile
保存编译顺序–>列出哪个文件先编译,哪个文件后编译
由configure脚本运行生成–>为什么编译时configure必须首先运行的原因

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

不同的源码文件,可能引用同一个头文件(比如stdio.h)
编译的时候,头文件也必须一起编译
编译器会在编译源码之前,先编译头文件—>保证了头文件只需编译一次
声明宏的#define命令,就不会被预编译。

第五步 预处理(Preprocessing)

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

extern int fputs(const char *, FILE *);extern FILE *stdout;int main(void){    fputs("Hello, world!\n", stdout);    return 0;}

有所忽略,只截取了fputs和FILE的声明
上面代码的头文件没有经过预编译,而实际上,插入源码的是预编译后的结果。编译器在这一步还会移除注释。
完成之后,就要开始真正的处理了。

第六步 编译(Compilation)

预处理之后,编译器就开始生成机器码
某些编译器来说,还存在一个中间步骤,会先把源码转为汇编码(assembly)然后再把汇编码转为机器码

.file   "test.c"    .section    .rodata.LC0:    .string "Hello, world!\n"    .text    .globl  main    .type   main, @functionmain:.LFB0:    .cfi_startproc    pushq   %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    movq    stdout(%rip), %rax    movq    %rax, %rcx    movl    $14, %edx    movl    $1, %esi    movl    $.LC0, %edi    call    fwrite    movl    $0, %eax    popq    %rbp    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE0:    .size   main, .-main    .ident  "GCC: (Debian 4.9.1-19) 4.9.1"    .section    .note.GNU-stack,"",@progbits

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

第七步 连接(Linking)

编译器在内存中生成了可执行文件
对象文件还不能运行,必须进一步转成可执行文件
转码结果,会发现其中引用了stdout函数和fwrite函数
程序要正常运行,还必须有stdout和fwrite这两个函数的代码,C语言的标准库提供

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

这种通过拷贝,将外部函数库添加到可执行文件的方式,叫做静态连接,后文会提到还有动态连接(dynamic linking)

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

第八步 安装(Installation)

连接是在内存中进行的,编译器在内存中生成了可执行文件
下一步,必须将可执行文件保存到用户事先指定的安装目录
这一步还必须完成创建目录保存文件设置权限等步骤。这整个的保存过程就称为"安装"(Installation)。

第九步 操作系统连接

可执行文件安装后,必须以某种方式通知操作系统可以使用这个程序了
例如:希望双击txt文件,该程序就会自动运行。
要求在操作系统中,登记这个程序的元数据:文件名、文件描述、关联后缀名等等
Linux系统中,这些信息通常保存在/usr/share/applications目录下的.desktop文件中。另外,在Windows操作系统中,还需要在Start启动菜单中,建立一个快捷方式。
这些事情就叫做”操作系统连接”。make install命令,就用来完成”安装”和”操作系统连接”这两步。

第十步 生成安装包

编译器将上一步生成的可执行文件,做成可以分发的安装包
通常是将可执行文件(连带相关的数据文件),以某种目录结构,保存成压缩文件包,交给用户

第十一步 动态连接(Dynamic linking)

开发者可以在编译阶段选择可执行文件连接外部函数库的方式,到底是静态连接(编译时连接),还是动态连接(运行时连接)

静态连接

就是把外部函数库,拷贝到可执行文件中
好处是:
适用范围比较广,不用担心用户机器缺少某个库文件
缺点是:
安装包会比较大,而且多个应用程序之间,无法共享库文件

动态连接

外部函数库不进入安装包,只在运行时动态引用
好处是:
安装包会比较小,多个应用程序可以共享库文件
缺点是:
用户必须事先安装好库文件,而且版本和安装位置都必须符合要求,否则就不能正常运行。

现实中,大部分软件采用动态连接,共享库文件。这种动态共享的库文件,Linux平台是后缀名为.so的文件,Windows平台是.dll文件,Mac平台是.dylib文件。

原创粉丝点击