【cpp查漏补缺】1-cpp是如何跑起来的?

【cpp查漏补缺】1-cpp是如何跑起来的?

spiritTrance

总概

本文将介绍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
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── add.cpp
├── main.cpp
├── mul.cpp
├── add.o
├── mul.o
├── lib
│ ├── libcalc.a
│ ├── libcalc.so
│ ├── libadd.a
│ ├── libadd.so
│ ├── libmul.a
│ └── libmul.so
└── main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 生成目标文件
gcc -o add.o -c add.cpp

# 静态库
ar -rcs lib/libcalc.a add.o mul.o # 使用ar(archive)工具创建,其中mylib.a是静态库名
gcc -o main main.cpp -L ./lib -lcalc # -L(Library)代表静态库路径,-l(link)是要链接的静态库

# 动态库
gcc -shared -o libcalc.so add.o mul.o # -shared 表示创建动态库,mylib.so是动态库名
gcc -o main main.cpp -L ./lib -lcalc # 与静态库一样
ldd main # 查看依赖关系,通常会发现新加的动态库找不到依赖
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`/lib
export LD_LIBRARY_PATH # 修改LD_LIBRARY_PATH变量使得能够查找到依赖,注意该方法只对当前终端有效,如果希望能够永久生效,需要修改环境变量
ldd main # 再次查看
./main # 执行

这里需要强调的是,库名需要用mylib,对应的文件名是libmylib.alibmylib.so,也就是说,搜索库的时候会在前面加上lib,后面加上.a.so后缀。然后会优先搜索动态库,其次搜索静态库。

下面介绍一下多个库链接以及动态库和静态库混合链接的情况。

1
2
3
4
5
6
7
8
9
10
# 多个静态库链接
gcc -static -o main main.cpp -L ./lib -ladd -lmul
gcc -static -o main main.cpp ./lib/libadd.a ./lib/libmul.a

# 多个动态库链接
gcc -Bdynamic -o main main.cpp -L ./lib -ladd -lmul

# 动态库和静态库混合链接
gcc main.cpp -L ./lib -Wl,-Bstatic -ladd -Wl,-Bdynamic -lmul # 第一种方式,先链接静态库,后链接动态库
gcc main.cpp -L ./lib -Wl,-Bdynamic -lmul -Wl,-Bstatic -ladd -Wl,-Bdynamic #第二种方式,先链接动态库,后链接静态库,然后在最后加上`-Wl,-Bdynamic`使用动态连接的命令

装载

最后,程序如果需要执行,需要装载器将可执行文件载入内存中,进行执行。其中仍然有一些细节,但不在本章讨论之类。如果希望了解更多的细节,请参考《深入理解计算机系统》。

总结

在本文最后,我们再次总结一下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.
推荐阅读
【cpp查漏补缺】2-Makefile入门 【cpp查漏补缺】2-Makefile入门 【cpp查漏补缺】3-CMake入门 【cpp查漏补缺】3-CMake入门 【MIT6.172】学习笔记(1) 【MIT6.172】学习笔记(1)
 Comments