C语言:编译和链接 📅 2026/8/23 6:33:45 文章目录前言本文旨在系统性地介绍C语言中的编译和链接。一、翻译环境和运行环境二、翻译环境2.1、预处理预编译2.2、编译2.2.1、词法分析2.2.2、语法分析2.2.3、语义分析2.2.4、生成汇编代码2.3、汇编2.4、链接三、运行环境前言本文旨在系统性地介绍C语言中的编译和链接。一、翻译环境和运行环境在ANSI C标准美国国家标准协会制定的 C 语言标准中一个C程序的实现过程被划分为两个独立的环境①翻译环境是指将源代码.c 文件转换为可执行机器指令二进制代码的阶段。它发生在程序运行之前由编译器完成。②运行环境是指程序被实际执行的阶段。它发生在程序加载到内存并开始运行时。二、翻译环境翻译环境由编译和链接两个大阶段组成其中编译又分为预处理预编译、编译、汇编三个子过程。链接阶段编译阶段汇编编译预处理.c 源文件预处理 (cpp).h 头文件.i 文件编译 (cc1).s 汇编文件汇编 (as).o/.obj 目标文件链接器 (ld)链接库(libc.a 等)可执行程序(.exe / a.out)注意事项①独立编译多个 .c 文件会各自独立经过编译器处理每个文件生成对应的目标文件。②头文件处理方式头文件的内容不会独立编译而是在预处理阶段被 #include 指令展开到 .c 文件中。展开后的代码和 .c 文件的原始代码一起构成完整的编译单元最终编译成一个目标文件。③目标文件后缀生成的中间文件目标文件后缀因平台而异Windows.obj、Linux / Unix.o④链接合并所有目标文件.obj / .o和所需的链接库一起交给链接器处理最终生成一个可执行程序。⑤链接库链接库包含支持程序运行的基本函数集合如运行时库或第三方提供的库。2.1、预处理预编译由预处理器进行源文件.c和头文件.h会被处理成 .i 为后缀的中间文件。在 gcc 环境下对 test.c 文件预处理后的 .i 文件命令如下gcc-Etest.c-otest.i部分含义gcc调用 GCC 编译器-E只执行预处理不进行编译、汇编、链接test.c输入的源文件-o test.i指定输出文件名为 test.i预处理主要处理的内容处理内容说明宏定义展开删除 #define展开所有宏定义条件编译处理 #if、#ifdef、#elif、#else、#endif 等指令头文件包含将 #include 的头文件内容插入到该位置递归处理删除注释移除 // 和 /* */ 注释添加调试信息添加行号和文件名标识便于后续编译器生成调试信息保留 #pragma保留 #pragma 指令供编译器后续使用预处理后的 .i 文件所有宏已展开、头文件已插入。当不确定宏定义或头文件是否正确时查看 .i 文件是最直接的验证方式。2.2、编译由编译器进行编译过程包括词法分析、语法分析、语义分析、生成汇编代码。在 gcc 环境下对 test.i 文件预处理后的 .s 文件命令如下gcc-Stest.i-otest.s部分含义gcc调用 GCC 编译器-S只进行编译生成汇编代码不进行汇编和链接test.i输入的预处理文件-o test.s指定输出文件名为 test.s2.2.1、词法分析由扫描器编译器的内部组件进行将源代码程序输入扫描器进行词法分析把代码中的字符分割成⼀系列的记号关键字、标识符、字面量、运算符、特殊字符。示例array[index](index4)*(26);分解为 16 个记号类型记号个数具体内容标识符3array、index、index运算符4、、*、字面量34、2、6特殊字符6[、]、(、)、(、)2.2.2、语法分析由语法分析器编译器的内部组件进行对扫描产生的记号进行语法分析从而产生语法树。这些语法树是以表达式为节点的树。示例array[index](index4)*(26);赋值表达式 ()下标表达式 (array[index])乘法表达式 (*)arrayindex加法表达式 ()加法表达式 ()index4262.2.3、语义分析由语义分析器编译器的内部组件进行对表达式的语法层面分析。编译器所能做的分析是语义的静态分析。包括①声明与类型匹配检查变量是否已声明类型是否匹配②类型转换检查类型转换是否合法如 int → double③错误报告 报告语法分析无法检测的语义错误。2.2.4、生成汇编代码由代码生成器编译器的内部组件进行生成汇编代码.s文件。2.3、汇编由汇编器进行将汇编代码.s 文件逐条翻译成机器指令二进制代码生成目标文件.o / .obj。根据汇编指令和机器指令的对照表一一的进行翻译不做任何优化。gcc-ctest.s-otest.o部分含义gcc调用 GCC 编译器驱动-c只编译和汇编不进行链接test.s输入的汇编文件-o test.o指定输出目标文件名为 test.o2.4、链接链接是将多个目标文件.o / .obj和库文件合并成可执行程序的过程核心任务是解决多文件之间的符号引用问题。每个 .c 文件独立编译成目标文件时编译器不知道其他文件中定义的函数和变量的地址因此会将这些引用暂时搁置等待链接阶段修正。链接的过程①地址和空间分配为所有目标文件分配内存地址空间②符号决议查找每个符号函数、变量的定义位置③重定位将引用符号的地址修正为真正的内存地址。示例编译时test.o调用 Add 的指令的地址被暂时搁置占位符链接时链接器在 add.o 中找到 Add 函数的实际地址重定位后将 test.o 中调用 Add 的指令修正为实际地址。test.c#includestdio.hexternintAdd(intx,inty);// 声明外部函数externintg_val;// 声明外部全局变量intmain(){inta10;intb20;intsumAdd(a,b);// 调用外部函数printf(%d\n,sum);printf(%d\n,g_val);// 使用外部变量return0;}add.cintg_val2022;intAdd(intx,inty){returnxy;}三、运行环境程序运行分为四个阶段加载 → 启动 → 执行 → 终止。①加载程序必须被加载到内存中才能运行。有操作系统由操作系统完成独立环境嵌入式手动安排或直接置入只读内存。②启动程序加载后从 main 函数开始执行。③执行程序运行时使用栈区stack存储局部变量和函数返回地址静态区static存储全局变量和静态变量值在整个程序运行期间保持。④终止正常终止main 函数正常返回意外终止程序崩溃或异常退出。