C/C++ 项目构建过程
- 预处理:预处理器将 C/C++源码(cpp, hpp, c, h, inl 等)中的头文件嵌入进去(查找包含文件),展开由 define 定义的宏,移除注释并解析条件编译指令,最终生成一个不包含预处理代码的
.i文件:g++ -E main.cpp -o main.i。 - 汇编:词法、语法、语义的分析,优化并编译为汇编代码:
g++ -S main.i -o main.s或g++ -S main.cpp -o main.s。 - 编译:将汇编代码翻译成机器指令,打包成目标文件。使用
-c进行汇编得到.o文件,不是一个文本文件,并不能直接查看其内容。但是可以通过反汇编指令objdump -d main.o将机器码翻译回汇编代码进行查看。 - 链接:将所需的静态库打包并嵌入到可执行文件中,Linux 下是 ELF,Windows 下是 EXE。注意对于静态库,链接器是单项扫描。假设 main 依赖静态库 la,静态库 la 又依赖静态库 lb,那么链接的顺序是
g++ main.o la lb,当然了,现代链接器可能对这一点进行了优化,也就是不需要强调链接的顺序也能正确链接。在 Linux 系统中,静态库通常以.a作为后缀,并以lib开头,例如libxxx.a。如果有动态库的话,链接器会标记引用,记录下动态库的位置。 - 加载:运行可执行文件时,操作系统的加载器会解析可执行文件中的引用标记,在标准搜索路径中查找系统中的动态库(Linux 下的
.so、Windows 下的.lib、MacOS 下的.dylib,在编译时被编译为位置无关代码Position Independent Code)并将其加载到内存中的任意可用位置,实现多进程共享。
动态库与静态库
以 Linux 系统为例
(1)静态库构建过程
假设现在有三个文件 main.cpp, add.cpp, minus.cpp,我需要将后面两个编译成静态库,下面是命令行实现源文件的构建与加载运行过程:
g++ -c add.cpp minus.cpp main.cpp ar rcs libmath.a add.o minus.o g++ main.o libmath.a -o exec /exec
上面的命令过程是:先编译所有的 cpp 文件,每个 cpp 文件生成对应的目标文件 .o,然后使用 ar 命令将 add 和 minus 打包成数学静态库 libmath.a,接着将 main 和 libmath 链接为可执行文件 exec,最后直接运行可执行文件。
(2)动态库的构建
和上面一样的例子
g++ -c -fPIC add.cpp minus.cpp g++ -shared -o libmath.so add.o minus.o g++ -c main.cpp g++ main.o -L. -lmath -o exec
生成目标文件,PIC 告诉编译器:生成的代码不要依赖于被加载到内存中的绝对地址。接下来由目标文件创建动态库。第 4 行命令告诉链接器记录引用,-L. 告诉编译器在当前目录中搜索动态库,-lmath 是告诉编译器链接标准命名为 math 的动态库,标准命名 lib<标准>.so,math 前面的 l 表示跨平台的快捷方式。可以使用命令 ldd exec(list dynamic dependencies)来查看 exec 所需的动态库。


