📝前言: 这篇文章主要讲解一下C语言的编译和链接,帮我们更好的理解程序的执行过程,更好的理解计算机系统。
在编写C语言程序时,我们通常会写一个或多个.c文件(源代码文件)。计算机并不能直接理解这些文本文件,需要将它们转换为机器可以执行的二进制文件。这个过程分为两个主要步骤:
.c文件)转换为目标文件(.o或.obj文件)。.exe或.out文件)。下面我们详细讲解这两个过程。
编译是将C语言源代码转换为机器代码的过程。它分为以下几个步骤:
预处理阶段,源文件和头文件会被处理成以.i为后缀的文件。预处理主要处理源文件中以#开头的预编译指令,比如:
#define,并展开所有宏定义。假设代码中有#define PI 3.14159,预处理后代码中所有的PI都会被替换为3.14159。#if、#ifdef、#elif、#else、#endif等条件编译指令,根据条件决定代码的取舍。#include预编译指令,将包含的头文件内容插入到该指令的位置,这个过程是递归进行的。如果test.c中#include "stdio.h",预处理时stdio.h的内容会被插入到#include所在的位置。#pragma指令:保留#pragma编译器指令,供后续编译器使用。在gcc环境下,可以使用gcc -E test.c -o test.i命令查看预处理后的结果。
示例:
#include <stdio.h>
#define PI 3.14159
int main() {
printf("PI = %f\n", PI);
return 0;
}预处理后,#include <stdio.h>会被替换为stdio.h文件的内容,PI会被替换为3.14159。
编译过程是将预处理后的文件进行词法分析、语法分析、语义分析及优化,生成相应的汇编代码文件,使用gcc -S test.i -o test.s命令。
汇编代码一般具备的信息:
MOV)、算术运算(ADD、SUB)、逻辑运算(AND、OR)、跳转(JMP、JE)等JMP、JE、JNE 等)来实现。下面通过array[index] = (index + 4) * (2 + 6);这段代码来看看编译过程:
array、[、index…6、)等16个记号。


汇编器将汇编代码转变成机器可执行的指令,每一个汇编语句几乎都对应一条机器指令。使用gcc -c test.s -o test.o命令完成汇编过程。汇编器会根据汇编指令和机器指令的对照表进行翻译,这个过程不做指令优化。
链接是将多个目标文件和库文件合并,生成最终可执行文件的过程。链接器的主要任务包括:
在编译过程中,每个源文件会生成一个目标文件。如果多个文件之间有函数调用或变量引用,链接器需要解析这些符号(函数名、变量名等),确保它们能够正确关联。
示例:
main.c中调用了math.c中的add函数。add函数的定义,并将其与main.c中的调用关联起来。目标文件中的地址通常是相对地址。链接器会将这些相对地址转换为最终可执行文件中的绝对地址。
假设有test.c和add.c两个文件,test.c中使用了add.c中的Add函数和g_val变量。每个源文件单独编译生成对应的目标文件,test.c生成test.o,add.c生成add.o。在编译test.c时,并不知道Add函数和g_val变量的地址,所以暂时搁置调用Add指令的目标地址和g_val的地址。链接器会根据引用的符号Add在其他模块中查找Add函数的地址,然后将test.c中所有引用到Add的指令重新修正,让它们的目标地址为真正的Add函数的地址,对于全局变量g_val也采用类似方法修正地址,这个过程就是重定位。
链接器将所有目标文件和库文件合并,生成一个可执行文件(如a.out或program.exe)。这个文件可以直接在操作系统中运行。
以下是一个简单的示意图,展示了从源代码到可执行文件的过程:

在实际开发中,我们通常使用编译器(如gcc)来自动完成编译和链接的过程。例如:
gcc main.c math.c -o program这条命令会:
main.c和math.c,生成目标文件。program。
翻译环境就是由上面提到的两个过程:编译和链接组成。而编译又可以进一步细分为预处理、编译、汇编三个子过程。 总结一下:
#开头的预编译指令程序在翻译环境生成可执行文件后,就进入运行环境:
main函数。main函数执行完毕返回,也有可能因为意外情况终止。