less than 1 minute read

编译器

编译器,是一种将高级语言编译为能够被机器识别和执行的机器码的工具。

我们以C++语言为例,我们平时敲代码时或许是这样写的:

#include<iostream>
int main() {
    std::cout<<"hello.world"<<endl;
    return 0;
}

这是一种高度抽象的语言,方便我们人类阅读编写,但是机器并不能直接识别运行。

  1. 一般我们会通过编译器,先让C++语言经过预编译过程,这个过程主要是展开以#开头的库文件,将他全部代入源码当中;
  2. 接着会进入正式的编译过程,这个过程主要进行词法分析,语法分析,语义分析,有一种名为语义树的数据结构可以帮助实现这个过程,最终还会通过一张对照表将代码转化为汇编代码
  3. 然后是汇编过程,到这一步就会汇编器as会将代码生成机器能够识别的机器码了。这个过程产生的文件.o是目标文件/对象文件 ,虽然内容已经接近于可直接运行了,但是这个文件是非常残缺的,里面的各种地址并没有确定,而且我们实际编译的项目往往会生成多个目标文件,它们互相调用的外部函数也没有传入。
  4. 所以我们还需要最后一个过程:链接,这个过程会调用链接器将所有中间文件以及一些系统运行库合并并调整,最终拼出了我们的可执行文件(ELF)

从刚才我们看到,这个过程已经出现了链接器了,所以我们平时常常讲的一些编译器工具如gcc, clang可以认为是已经包含了链接器了,我们平时说的这些工具更多地是指编译器的驱动程序,它们能够自动调用汇编过程各个工具,组成完整的工具链。

链接器

链接器的作用刚才已经形象说明了,这里简单讲下它的两个核心作用:

  1. 符号解析 每个目标文件在编译时都是独立的,当你在一个文件里面调用了另一个文件的函数时,编译过程并不能识别,而是将它标记为一个符号,然后在链接过程,链接器会识别这些符号并将他们解析
  2. 重定位 这个过程链接器会修正代码和数据地址。因为每个目标文件编译时都默认从0开始编译,链接器会将地址重新排列,使代码写入内存时不重叠

调试器

调试器顾名思义,就是方便你进行调试代码的,比如设断点、单步执行,观察变量值,这些都是通过调试器来实现的。 目前我们常见的调试器有GDB, LLDB等。需要注意调试器并不会参与代码的编译和执行。


简单概括一下就是:你写的高级语言并不能直接被机器识别,它们都必须通过编译器转换为机器码,通过链接器将所有目标文件和系统运行库像“胶水一样”链接起来,最后变成一个双击就能运行的可执行文件。

关于编译器、链接器的更深层次的作用,可以参考下一篇文章:项目中的多种语言是如何互相调用的

Leave a comment