【转载】C++编译过程
c++编译过程
c++ 编译过程
在介绍编译器之前,先简单地说一下 c++ 的编译过程,以便理解编译器的工作。
编译(compiling)并不意味着只创建仅仅一个可执行文件。创建一个可执行文件是一个多级过程,其中最重要的过程是预处理(preprocessing),编译(compliation)和链接(linking)。从源代码文件到一个可执行文件的整个过程,最好的说法是 build(中文翻译的话,有叫生成,有叫编译链接,也有叫构建)。compiling 仅仅是 build 过程的一部分,但你经常会碰到许多人把 compile 指代整个过程。通常情况下,你不需要为这几个过程运行单独的命令,编译器自己会调用,如预处理器。
2.1 预处理
build 过程的第一步就是编译器运行 c 预处理器,目的是对代码文件进行文本上的处理。它会处理头文件包含指令(#include),条件编译指令(#ifdef……#endif)和宏(#define),这些指令叫做预处理指令,都以井字符 # 开头。编译器本身是绝对看不到这些预处理指令的。
比如:
#include <iostream>
这句代码会告诉预处理指令,要把 iostream 的文件内容抓去到当前文件,你每包含一个头文件,它就会把这个头文件的内容粘贴到这个文件中,然后把 #include 指令移除。
#define my_name "alex"
宏就是一个被其它内容(可能比较复杂)替换掉的字符串内容,此时预处理器会把下面的代码:
cout << "hello" << my_name << endl;
展开成:
cout << "hello" << "alex" << endl;
由于预处理器在编译器之前处理代码,它也可以用来移除代码——有时,你会要在代码里执行某些测试代码。你可以告诉预处理器,如果定义了某个宏,则包含某些代码。然后,如果你想执行这个代码,就定义这个宏,否则就移除掉这个宏的定义。
#include <iostream> #define debug using namespace std; int main() { int x; int y; cout << "enter value for x: "; cin >> x; cout << "enter value for y: "; cin >> y; x *= y; #ifdef debug cout << "x: " << x << '\n' << "y: "<< y; #endif }
如果你不想执行变量的打印,那么只需简单注释掉 #define debug 就行。
同样地,你也可以用 #ifndef 来改变条件——如果没有定义……这个方法通常用在引入多个头文件的时候。
2.2 编译
编译意味着把一个源文件(.cpp)转变成一个对象文件(object,.o 或 .obj)。
一个对象文件会把你程序里的每一个函数,封装成一个计算机处理器能理解的形式——机器指令(machine language instructions)。每一个源文件都是单独编译过的,即对象文件包含的机器代码都是编译过的源代码。比如,你有三个源文件,经过编译,生成了三个对象文件,每一个对象文件都包含了各自对应的机器代码。
但你还不能运行它们,这时候,就需要链接器了。
2.3 链接
链接(linking),是把一堆对象文件和库(有时也可能仅仅是一个对象文件,但也需要链接)创建成一个单独的可执行文件(比如 .exe 或 .dll)。
链接器通过一种适当的格式创建一个可执行的文件,并传递每个独立的对象文件内容到一个可执行的结果。链接器也处理含有对象文件源代码之外的其它函数的引用,比如 c++ 标准库里的函数。当你调用了一个 c++ 标准库的函数,如 cout << “hi”,你就在使用一个自己代码中没有定义的函数,它被定义在一个相关的对象文件中,但这是由编译器提供的,并不属于你。在编译时,编译器知道这个函数是有效的,因为你引出了 iostream 头文件,但由于这个函数不是 cpp 文件的一部分,编译器就会在调用树(call tree)留下一个存根(stub),链接器会遍历对象文件,针对每一个存根,它会找到正确的函数地址,然后从已链接过的其它对象文件中,用正确的地址替换掉对应的存根。
这个过程有时也叫做修正(fixup)。当你把你的程序分离成多个源文件时,你就会利用链接器来修正所有在源文件中调用过的函数。如果链接器找不到这个函数的位置,它就会生成一个 undefined function error,即便代码被编译器通过了,也不意味着代码是正确的。链接器是首先以全局的视角来探测这种错误的。
上一篇: 别再找了,宝宝们想要的臊子汤配方在这里