【cpp查漏补缺】1-cpp是如何跑起来的?
总概
本文将介绍cpp从源代码到可执行文件的构建流程。
预处理
预处理一步仅仅是简单的文本替换,如包含文件#include
,宏展开#define
等。预处理的结果是一个经过修改的源代码,通常以.i文件的扩展名保存。你可以尝试下列指令来生成预处理后的文件:
1 | gcc -E my_program.cpp -o my_program.i |
编译
编译这一步则是将预处理后的源文件转换成汇编语言,常见的指令集,如x86。
cpp主流编译器介绍
- GCC(GNU Compiler Collection):准确来说,GCC是由GNU项目开发的编译器套件,包括C,C++,Fortran等。而g++是C++的编译器,gcc是C的编译器(也可以编译C++,但一般用g++代替)
- MinGW(Minimalist GNU for Windows)是一套用于Windows操作系统的开发工具集,它允许开发人员使用GNU工具链在Windows上开发应用程序,包括编译和构建C、C++等编程语言的应用程序。
- MSVC(MicroSoft Visual C++):是指 Microsoft Visual C++,是 Microsoft 的 C++ 编译器和开发工具集。
- Clang:是LLVM(Low-Level Virtual Machine)项目的一部分。Clang 可以在多种操作系统上运行,包括各种Unix-like系统(如Linux和macOS)以及Windows。
- LLVM:是一个开源的编译器基础设施项目,它包括一组模块化的编译器技术和工具,用于开发和优化程序的编译和执行。Clang为LLVM的前端。除此之外,还包括一种虚拟指令集和优化器,允许开发者构建自定义编程语言。
简单总结,主流的编译器为gcc(g++),msvc(中的c++编译器)和clang三种。而LLVM是一个基础框架,可以帮助开发编译器。
生成汇编代码
1 | gcc -S -march=x86-64 my_program.cpp -o my_program.S |
注意-march
选项用于指定指令集,这里指定的是x86-64。可以选择将x86-64替换成其他选项,如果不存在,gcc会提示非法,并展示所有合法选项。
汇编
这一步的任务由汇编器完成:将汇编语言转换成机器代码。除此之外,汇编语言还承担着翻译伪指令的任务。
> 伪指令:相关指令集并未提供的指令,伪指令的出现原因是为了编译器开发者的方便而发明出来的。最后需要由汇编器完成伪指令翻译的任务。
链接
链接是将多个目标文件和库文件组合成一个可执行文件的过程。
链接器的任务
链接器的任务主要有:
- 符号解析:解析各个目标文件中的符号引用,以确定它们对应的符号定义,并建立符号表。这里解释一下符号:符号包括变量名,函数名,类名等。C中有一个关键字
extern
,即提醒该符号需要在外部文件中查找。另外,如果一个cpp源文件没有包含函数声明而调用了某一函数,那么这一文件是无法通过编译这一步的。如果修过《编译原理》相关课程,这一原因不难想到:无法确定参数列表和返回值,也就无法生成正确的ABI调用。 - 地址解析:计算各个符号在可执行文件中的地址。每一个目标文件都是假设从虚拟地址空间的0x00000000开始,多个目标文件组合成一个文件,自然要重新计算地址,也就是下面的重定位步骤。
- 重定位:调整目标文件中的代码和数据,使其正确地引用其他目标文件中的符号。
- 合并:将所有目标文件合并成为可执行文件。
考虑到本文的出发点,本文主体主要介绍库文件如何创建和链接。本文仅简单介绍了链接的具体步骤。如果对链接的具体细节感兴趣,请参考《深入理解计算机系统》相关章节。
库文件
库文件(Library Files)是一种包含已编译二进制代码的文件,它们包含了可重用的程序模块或函数。一般来说,库文件分为静态库和动态库。
- 静态库
- 扩展名:
.a
(Linux)或.lib
(Windows) - 在编译时加入可执行文件中
- 优点:在于程序执行速度快
- 缺点:是增大了可执行文件大小,库文件的更新会引起可执行文件的重新编译
- 扩展名:
- 动态库
- 扩展名:
.so
(Linux)或.dll
(Windows)或.dylib
(macOS) - 不会在编译期加入可执行文件,而是在程序运行时加载到内存中
- 优点:是多个程序可以共享动态库,有助于节省内存。库文件的更新,不需要重新编译可执行文件,只需替换库文件即可
- 缺点:是由于在运行时要加载库文件,程序启动速度较慢。
- 扩展名:
下面介绍静态库和动态库的创建和链接,这里先给出目录:
这里先给出目录:
1 | . |
1 | # 生成目标文件 |
这里需要强调的是,库名需要用mylib
,对应的文件名是libmylib.a
或libmylib.so
,也就是说,搜索库的时候会在前面加上lib
,后面加上.a
或.so
后缀。然后会优先搜索动态库,其次搜索静态库。
下面介绍一下多个库链接以及动态库和静态库混合链接的情况。
1 | # 多个静态库链接 |
装载
最后,程序如果需要执行,需要装载器将可执行文件载入内存中,进行执行。其中仍然有一些细节,但不在本章讨论之类。如果希望了解更多的细节,请参考《深入理解计算机系统》。
总结
在本文最后,我们再次总结一下gcc常用的选项:
- -o
:指定输出文件
- -E
:对代码仅进行预处理,生成*.i
文件
- -S
:生成汇编代码,生成*.S
文件
- -march
:指定指令集
- -c
:对源代码进行预处理,编译和汇编,但不链接。(即目标代码.o
)
- -L
:指定库所在的目录
- -I
:指定头文件所在目录
- -l
:指定库名,注意对于lib*.so或lib*.a类似的库,有两种写法:-l:lib*.a
或-l*
- -static
或-Bstatic
:指定静态库链接
- -Bdynamic
:指定动态库链接
- -Wl,
:表示将额外的参数传递给链接器,如-Wl,-Bstatic
表明需要接下来链接静态库
参考资料
《深入理解计算机系统》
- Title: 【cpp查漏补缺】1-cpp是如何跑起来的?
- Author: spiritTrance
- Created at: 2023-10-01 12:33:07
- Updated at: 2024-01-06 20:07:25
- Link: https://spirittrance.github.io/2023/10/01/cpp_1_链接/
- License: This work is licensed under CC BY-NC-SA 4.0.