文章作者:Tyan
博客:noahsnail.com | CSDN | 简书
1. 引言
C++程序从源代码到可执行程序是一个复杂的过程,其流程为:源代码 --> 预处理 --> 编译 --> 优化 --> 汇编 --> 链接 --> 可执行文件
,本文以一段C++代码为例,按执行顺序来描述这个过程。
2. 源代码
源代码文件分为两个,hello.h
、hello.cpp
和main.cpp
,代码如下:
hello.hpp
1
2
3
4
5
6
void hello();hello.cpp
1
2
3
4
5
6
7
using namespace std;
void hello() {
cout << "Hello, world!" << endl;
}main.cpp
1
2
3
4
5
6
int main(int argc, char *argv[]) {
hello();
return 0;
}
3. 预处理
预处理是指C++程序源代码在编译之前,由预处理器(Preprocessor)对C++程序源代码进行的处理。在这个阶段,预处理器会处理以#
开头的命令,处理完成之后会生成一个不包含预处理命令的纯C++文件,常见的预处理有:文件包含(#inlcude)、条件编译(#ifndef #ifdef #endif
)、提供编译信息(#pragma
)、宏替换(#define
)等。
使用g++
预处理main.cpp
的命令如下:
1 | [root@localhost:/workspace] $: g++ -E main.cpp -o main.ii |
-E
参数表示预处理后即停止,不进行编译,预处理后的代码送往标准输出,-o
指定输出文件。输出文件main.ii
的内容如下:
1 | # 1 "main.cpp" |
4. 编译
在编译过程中,编译器主要作语法检查和词法分析。通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
编译main.ii
的命令如下:
1 | [root@localhost:/workspace] $: g++ -S main.ii |
-S
参数表示编译后即停止,不进行汇编。对于每个输入的非汇编语言文件,输出文件是汇编语言文件。输出文件main.s
的内容如下:
1 | .file "main.cpp" |
5. 优化
优化是在编译过程中最重要的,也是最难的。它不仅与编译技术本身有关,而且跟机器的硬件环境也有很大的关系。优化可在编译的不同阶段进行,一类优化是对中间代码的优化,这类优化不依赖于具体的计算机,另一类优化是对目标代码的优化,这类优化与机器的硬件环境有关。
g++
编译器的编译优化参数为-O
,分为四级,分别为-O0
、-O1
、-O2
、-O3
,默认为-O0
。各级优化后的结果如下:
1 | # 默认优化,-O0 |
6. 汇编
汇编是把汇编语言代码翻译成目标机器指令的过程。
编译main.s
的命令如下:
1 | [root@localhost:/workspace] $: g++ -c main.s |
-c
参数表示编译或汇编源文件,但是不作连接,编译器输出对应于源文件的目标文件。输出文件为main.o
,使用nm -C main.o
命令来查看文件内容,文件内容如下:
1 | 0000000000000000 T main |
7. 链接
链接是将目标文件、启动代码、库文件链接成可执行文件的过程,得到的文件可以直接执行。经过汇编之后生成的目标文件main.o
是不可以直接执行的。链接命令如下:
1 | [root@localhost:/workspace] $: g++ main.o -o main |
从上面可以看出,只链接main.o
文件会报错,这是因为main.cpp
引用了hello.cpp
中定义的函数hello
,因此需要链接文件hello.cpp
才能生成可执行程序。重复上述过程,生成hello.o
,链接两个文件的命令如下:
1 | [root@localhost:/workspace] $: g++ main.o hello.o -o main |
经过链接,多个文件被链接成了单一的可执行文件main
,执行main
程序:
1 | [root@localhost:/workspace] $: ./main |
7.1 静态链接库
除了直接链接多个目标文件之外,还可以通过链接静态库生成可执行文件。静态链接库是编译器生成的一系列对象文件的集合,库中的成员包括普通函数,类定义,类的对象实例等。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。可执行文件生成之后,就不再需要静态链接库,即编译后的可执行程序不需要外部函数库的支持。但如果静态链接库发生改变,则可执行程序需要重新编译。静态链接库属于编译时链接。
我们再添加两个static.hpp
,static.cpp
,并修改main.cpp
,内容如下:
static.hpp
文件:1
2
3
4
5
6
void test();static.cpp
文件:1
2
3
4
5
6
7
using namespace std;
void test() {
cout << "static lib" << endl;
}main.cpp
文件:
1 | extern void hello(); |
编译汇编hello.cpp
、static.cpp
之后可以得到两个文件hello.o
和static.o
,linux系统中的命令ar
,可以将多个目标文件打包成为一个单独的文件,这个文件被称为静态库。生成静态库的命令如下:
1 | [root@localhost:/workspace] $: ar -r libstatic.a hello.o static.o |
查看libstatic.a
的内容:
1 | [root@localhost:/workspace] $: nm -C libstatic.a |
通过静态链接库生成可执行程序main
并执行:
1 | [root@localhost:/workspace] $: g++ main.o libstatic.a -o main |
另一种命令方式:
1 | [root@localhost:/workspace] $: g++ -L ./ main.cpp -lstatic -o main |
Linux静态库的命名惯例是名字以三个字母lib
开头并以後缀.a
结束。所有的系统库都采用这种命名惯例,并且它允许通过-l(ell)
选项来简写命令行中的库名。-lstatic
中的-l
是要求编译器在系统库目录下查找static
库,static
是libstatic.a
的简写。-L
参数用来指定要具体的查找目录,如果缺少这个参数,则只会在系统库目录下查找static
,会报错。错误如下:
1 | [root@localhost:/workspace] $: g++ main.cpp -lstatic -o ltest |
7.2 共享库
共享库(Windows叫动态链接库)是编译器以一种特殊的方式生成的对象文件的集合。对象文件模块中所有地址(变量引用或函数调用)都是相对而不是绝对的,这使得共享模块可以在程序的运行过程中被动态地调用和执行。共享库属于运行时链接。当使用共享库时,只要共享库的接口不变,共享库修改之后,不需要重新编译可执行程序。
创建dynamic.cpp
,内容如下:
1 | #include <iostream> |
编译hello.cpp
和dynamic.cpp
,-fpic
表示生成的对象模块采用浮动(可重定位)地址,pic
是位置无关代码(position independent code)的缩写。:
1 | [root@localhost:/workspace] $: g++ -c -fpic hello.cpp static.cpp |
使用-fpic
与不使用-fpic
生成的目标文件hello.o
:
1 | # 使用-fpic |
创建共享库dynamic.so
,-shared
表示生成共享目标文件。:
1 | [root@localhost:/workspace] $: g++ -shared hello.o dynamic.o -o libdynamic.so |
编译main.cpp
并链接共享库:
1 | [root@localhost:/workspace] $: g++ main.cpp libdynamic.so -o main |
执行main
:
1 | [root@localhost:/workspace] $: ./main |
报错是因为当前工作目录可能不在共享库的查找路径中,因此需要将当前目录添加到环境变量LD_LIBRARY_PATH
中:
1 | [root@localhost:/workspace] $: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./ |
查看链接静态库和共享库生成的两个可执行main
文件:
1 | # 共享库 |
8. 可执行文件
可执行文件指的是可以由操作系统进行加载执行的文件。在不同的操作系统环境下,可执行程序的呈现方式不一样。例如上面生成的main
就是Linux系统下的可执行文件,windows系统下的可执行文件一般为*.exe
。
参考资料
- https://wiki.ubuntu.org.cn/Compiling_Cpp
- https://tech.meituan.com/2015/01/22/linker.html
- http://notes.maxwi.com/3416/06/05/source-to-program/
- http://www.ruanyifeng.com/blog/2014/11/compiler.html
- https://blog.csdn.net/zhengqijun_/article/details/51881149
- https://www.cnblogs.com/Goldworm/archive/2012/05/21/2511910.html
- https://juejin.im/entry/5c0d23b35188253b7e7480db
- https://www.zhihu.com/question/280665935
- http://www.shanghai.ws/gnu/gcc_1.htm
- https://wiki.ubuntu.org.cn/Compiling_C
- https://www.cnblogs.com/sunsky303/p/7731911.html