读书《程序员的自我修养》-1

读书《程序员的自我修养》-1 源代码到可执行文件的编译过程概述通常使用gcc hello.c可以直接得到一个可执行文件。实际上这个简单命令背后包含了以下四个主要步骤预编译预处理→ 编译 → 汇编 → 链接这四个步骤将我们的源代码最终转换成可执行的程序每个步骤都有其特定的作用和生成的中间文件格式。详细步骤1. 预编译预处理作用处理源文件中的预处理指令包括展开宏定义#define处理条件编译#if、#ifdef、#endif插入头文件内容#include删除注释添加行号和文件名信息用于调试生成的文件格式.i执行命令gcc-Ehello.c-ohello.i# 或cpp hello.chello.i预处理后的文件特点不再包含任何#开头的预处理指令头文件的内容已经完全展开宏定义已被替换为实际值文件体积通常会显著增大2. 编译作用对预处理后的文件进行一系列分析处理包括词法分析将源代码分解为词法记号tokens语法分析构建抽象语法树AST语义分析检查类型匹配、变量声明等语义正确性代码优化进行各种优化以提高执行效率生成汇编代码将优化后的代码转换为汇编语言生成的文件格式.s执行命令gcc-Shello.i-ohello.s# 或直接从源文件gcc-Shello.c-ohello.s编译阶段的重要性这是编译器最复杂的阶段包含前端和后端前端负责分析源代码后端负责生成目标代码优化在此阶段进行可以显著提升程序性能3. 汇编作用将汇编代码转换为机器可执行的指令每条汇编语句几乎对应一条机器指令。生成的文件格式.oUnix/Linux或.objWindows执行命令as hello.s-ohello.o# 或gcc-chello.s-ohello.o快捷方式也可以直接从 C 源代码生成目标文件gcc-chello.c-ohello.o汇编器的特点翻译过程相对简单是一对一的映射关系生成的是二进制格式的机器码包含了符号表、重定位表等链接所需信息还不能直接执行需要链接后才能运行4. 链接作用将多个目标文件.o、库文件.lib、.so等链接在一起生成最终的可执行程序。主要任务符号解析解析符号引用确定每个符号的定义位置重定位调整代码和数据中的地址使其指向正确的位置库文件链接从静态库或动态库中提取所需的代码和数据生成的文件格式.outLinux或.exeWindows执行命令# 使用链接器ld-staticxxx.o yyy.o zzz.o# 或使用 gcc推荐gcc xxx.o yyy.o zzz.o-oprogram链接的重要性链接是编译过程的最后一步也是最复杂的一步分为静态链接和动态链接两种方式错误通常在此阶段暴露如未定义的符号现代软件开发中链接往往在不知不觉中完成完整转换流程文件格式在整个编译过程中的变化如下hello.c → hello.i → hello.s → hello.o → a.out / hello.exe (源代码) (预处理) (汇编) (目标文件) (可执行文件)各阶段的输入输出关系┌─────────────┐ │ hello.c │ 源代码 └──────┬──────┘ │ 预编译 ↓ ┌─────────────┐ │ hello.i │ 预处理文件 └──────┬──────┘ │ 编译 ↓ ┌─────────────┐ │ hello.s │ 汇编文件 └──────┬──────┘ │ 汇编 ↓ ┌─────────────┐ │ hello.o │ 目标文件 └──────┬──────┘ │ 链接 ↓ ┌─────────────┐ │ a.out │ 可执行文件 └─────────────┘GCC 编译选项详解常用编译选项选项说明停止阶段输出文件-E只进行预处理预编译.i-S只编译到汇编编译.s-c只编译到目标文件汇编.o无完整编译链接链接可执行文件一键查看中间文件# 保留所有中间文件gcc -save-temps hello.c-ohello# 生成的文件# hello.i (预处理文件)# hello.s (汇编文件)# hello.o (目标文件)# hello (可执行文件)调试和优化选项# 生成调试信息包含符号表gcc-ghello.c-ohello# 优化编译-O1, -O2, -O3gcc-O2hello.c-ohello# 同时优化和调试信息gcc-g-O2hello.c-ohello静态链接与动态链接静态链接gcc-statichello.c-ohello特点在链接时将所有库代码复制到可执行文件中可执行文件独立运行不依赖外部库文件体积较大更新库需要重新编译动态链接gcc hello.c-ohello# 或显式指定动态链接gcc -dynamic-linker /lib64/ld-linux-x86-64.so.2 hello.c-ohello特点在运行时加载所需的动态库可执行文件体积小多个程序可共享同一动态库节省内存库的更新不影响已编译的程序常用分析工具1. 查看预处理结果gcc-Ehello.c-ohello.i2. 查看汇编代码gcc-Shello.c-ohello.s3. 查看目标文件信息# 查看文件格式filehello.o# 查看符号表nm hello.o# 查看段信息objdump-hhello.o# 查看详细信息objdump-xhello.o# 查看反汇编代码objdump-dhello.o# 查看文件大小size hello.o4. 查看可执行文件信息# 查看动态库依赖ldd hello# 查看可执行文件的段readelf-lhello# 查看可执行文件的符号nm-Dhello编译过程示例示例程序#includestdio.hintglobal_var100;intglobal_uninit_var;staticintstatic_var200;intmain(){printf(Hello, World!\n);return0;}完整编译过程# 1. 预处理gcc-Ehello.c-ohello.i# 2. 编译gcc-Shello.i-ohello.s# 3. 汇编gcc-chello.s-ohello.o# 4. 链接gcc hello.o-ohello# 5. 运行./hello总结步骤输入文件输出文件主要作用工具预编译.c.i处理宏定义、头文件包含等预处理指令cpp编译.i.s生成汇编代码cc1汇编.s.o/.obj生成机器指令的目标文件as链接.o.out/.exe链接库文件生成可执行程序ld/collect2补充说明编译器的组成┌──────────────┐ │ 源代码 │ └──────┬───────┘ │ ┌──────▼───────┐ │ 预处理器 │ (cpp) └──────┬───────┘ │ ┌────────────▼────────────┐ │ 编译器前端 │ (词法、语法、语义分析) │ (Front End) │ └────────────┬────────────┘ │ ┌────────────▼────────────┐ │ 优化器 │ (代码优化) │ (Optimizer) │ └────────────┬────────────┘ │ ┌────────────▼────────────┐ │ 编译器后端 │ (代码生成) │ (Back End) │ └────────────┬────────────┘ │ ┌──────▼───────┐ │ 汇编器 │ (as) └──────┬───────┘ │ ┌──────▼───────┐ │ 链接器 │ (ld) └──────────────┘文件大小对比以一个简单的 Hello World 程序为例文件类型大小说明hello.c~100B源代码hello.i~30KB预处理后包含头文件展开hello.s~2KB汇编代码hello.o~2KB目标文件机器码hello.exe(静态)~800KB静态链接可执行文件hello.exe(动态)~20KB动态链接可执行文件链接器的工作流程扫描阶段扫描所有目标文件和库文件建立符号表地址分配为每个段分配虚拟地址空间符号解析解析符号引用与定义的关系重定位修改代码和数据中的地址引用生成可执行文件输出最终的可执行文件