目录一. 编译的四个阶段1.1 预处理1.1.1 代码示例1.1.2 命令行宏定义1.1.3 条件编译的用处1.2 编译1.2.1 代码示例1.3 汇编1.3.1 代码示例1.4 链接1.4.1 代码示例二. 动态库和静态库2.1 什么是库2.2 库文件的命名规则2.3 动静态库2.4 动态链接和静态链接2.4.1 动态链接2.4.2 静态链接2.4.3 优缺点三. 实践操作3.1 ldd3.2 file3.3 使用静态链接编译3.3.1 静态库的安装3.3.2 验证3.4 动态库3.5 在技术上理解库一. 编译的四个阶段1.1 预处理预处理就是在真正编译之前先把头文件展开、宏替换、条件编译等工作处理好为后面的正式编译做准备。gcc -E code.c -o code.i #不指定输出文件名那么默认输出到标准输出 gcc → 使用 GCC 编译工具 -E → 只进行预处理 → 预处理完成后就停止不继续编译 code.c → 要处理的 C 源文件 -o → 指定输出文件名 code.i → 预处理后的结果文件1.1.1 代码示例1.1.2 命令行宏定义命令行宏定义指的是在编译程序时通过 GCC/G 的命令行参数-D来定义宏而不是在源代码中使用#define定义。g code.cpp -DM -o code g → 使用 C 编译器 code.cpp → 要编译的源文件 -DM → 在命令行中定义宏 M -o code → 指定生成的可执行文件名1.1.3 条件编译的用处1.2 编译在汇编阶段编译器会对代码进行词法分析、语法分析、语义分析以及代码优化等操作检查代码是否符合语言规则并将高级语言代码转换为对应的汇编代码。gcc -S code.i -o code.s #不指定文件名时则会默认生成 code.s 文件 gcc → 使用 GCC 编译工具 -S → 只编译生成汇编代码不继续进行汇编和链接 code.i → 预处理后的 C 语言文件 -o code.s → 指定输出文件名为 code.s1.2.1 代码示例1.3 汇编在汇编阶段汇编器会将汇编指令转换成对应的机器码并生成目标文件通常为.o文件。目标文件中已经包含机器指令但通常还不能直接运行因为其中可能存在尚未解析的函数引用、外部符号等信息需要等待后续的链接阶段进行处汇编生成的目标文件已经是二进制文件所以我们想打开的话大部分是乱码。gcc -c code.s -o code.o #不指定文件名时则会默认生成 code.o 文件 gcc → 使用 GCC 工具链 -c → 只生成目标文件不进行链接 code.s → 编译阶段生成的汇编文件 -o code.o → 指定输出文件名为 code.o1.3.1 代码示例1.4 链接经过汇编阶段后我们已经得到了目标文件.o其中包含机器指令。但是目标文件中可能还存在尚未解析的外部符号引用例如调用其他源文件中的函数或者调用标准库中的printf()函数。因此需要经过链接阶段由链接器将相关的目标文件和所需的库关联起来处理函数、变量的引用及地址等信息最终生成可执行程序。gcc code.o -o mycode #不指定 -o 时默认生成名为 a.out 的可执行文件。 gcc → 使用 GCC 工具链 code.o → 输入汇编阶段之后生成的目标文件 -o mycode → 指定输出文件名为 mycode1.4.1 代码示例二. 动态库和静态库2.1 什么是库库是将已经实现的功能代码组织起来供其他程序重复使用的代码集合。在 C/C 开发中我们通常通过头文件获得函数声明再通过链接使用库中提供的函数实现。根据链接方式的不同常见的库可以分为静态库和动态库。2.2 库文件的命名规则在 Linux 中库文件通常采用下面的命名方式lib 库名 文件后缀库文件名去掉前缀和后缀对应库名libm.ammlibm.sommlibc.socclibstdc.sostdcstdclibcalc.acalccalc2.3 动静态库静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名⼀般为.a动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库⼀般后缀名为“.so”,如 libc.so.6 就是动态库。gcc在编译时默认使用动态库。完成了链接之后,gcc就可以生成可执行文件,如下所示。gcc hello.o –o hello2.4 动态链接和静态链接2.4.1 动态链接在链接阶段不把动态库中的代码复制到我们的可执行文件里而是记录“我需要哪个动态库、哪个函数”等程序运行时再由系统找到并加载这个动态库然后确定函数的实际地址并调用。2.4.2 静态链接在链接阶段就把程序需要的静态库代码复制到最终的可执行文件中。2.4.3 优缺点对比静态库.a动态库.so程序大小较大因为库代码被放进可执行文件较小不需要把库代码完整放进去运行依赖依赖较少链接完成后通常不需要原来的静态库依赖动态库运行时动态库不能缺失内存占用多个程序分别带一份库代码可能产生重复多个程序可以共享同一个动态库节省内存更新维护库更新后通常需要重新链接程序库更新后兼容的情况下通常不需要重新编译程序启动/运行不需要运行时加载对应静态库运行时需要加载动态库三. 实践操作3.1 lddldd是 Linux 中用于查看可执行文件依赖哪些动态库的命令。在 Linux 下使用 GCC/G 进行正常编译并最终生成可执行文件时默认通常采用动态链接。3.2 filefile是 Linux 中用来查看文件类型的命令。ELF ↓ Linux 可执行文件格式 x86-64 ↓ 适用于 x86_64 CPU dynamically linked ↓ 采用动态链接 ld-linux-x86-64.so.2 ↓ 运行时负责加载、解析动态库3.3 使用静态链接编译在前面我们知道使用 g/gcc 编译时默认使用动态库如果我们想要静态库就要额外加选项 -staticg code.cpp -o mycode_static -static3.3.1 静态库的安装但是不排除有的系统下并没有安装静态库这时候就需要我们手动安装静态库。C程序 sudo apt update sudo apt install libc6-dev C程序 sudo apt install libstdc-dev3.3.2 验证我们会发现使用静态链接编译的可执行文件大小比动态链接编译的可执行文件大很多这也印证了我们之前的结论。3.4 动态库动态库又称为共享库为什么呢当可执行文件mycode依赖动态库时程序运行过程中不仅会加载mycode本身也会加载它依赖的动态库。动态库不是独立运行的而是被加载到进程地址空间中供程序调用其中的函数。若后续还有多个可执行文件依赖同一个动态库操作系统通常可以共享该动态库的代码部分因此不必在物理内存中重复保存多份相同的库代码从而节省内存。3.5 在技术上理解库在 C/C 项目中多个源文件通常会分别经过预处理、编译和汇编生成对应的.o目标文件然后由链接器将它们组合起来形成可执行程序。这样不仅方便模块化开发、提高编译效率还可以在不直接提供源代码的情况下将目标文件交给其他开发者使用。但如果目标文件数量很多管理起来就会比较繁琐因此可以将多个目标文件组织成库。其中静态库通常就是多个目标文件归档打包形成的而动态库则通过进一步链接生成。