完整编译流程与对应文件
源代码.c → 预处理.i → 编译.s → 汇编.o → 链接 → 可执行文件
详细文件类型说明
.c 文件 - 源代码文件
内容:你写的C代码
特点:人类可读
.i 文件 - 预处理后文件
生成命令:gcc -E hello.c -o hello.i
内容:展开所有 #include、#define、#if 后的纯C代码
特点:仍然是C代码,但没有预处理指令了
.s 文件 - 汇编代码文件
生成命令:gcc -S hello.c -o hello.s
内容:汇编语言代码
特点:CPU指令的文本表示,人类可读但难懂
.o 文件 - 目标文件(Object File)
生成命令:gcc -c hello.c -o hello.o
内容:二进制机器码 + 符号表 + 重定位信息
特点:机器码,但还不完整(等待链接)
查看命令:objdump -d hello.o 或 nm hello.o
.a 文件 - 静态库文件(Archive)
生成命令:ar rcs libmylib.a file1.o file2.o
内容:多个 .o 文件打包成一个文件
特点:链接时会被完全复制进可执行文件
示例:libm.a(数学库静态版)
.so 文件 - 动态库文件(Shared Object)
生成命令:gcc -shared -fPIC file.c -o libmylib.so
内容:可被多个程序共享的库代码
特点:运行时才加载,节省空间
示例:libc.so.6(C标准库动态版)
可执行文件(无扩展名或 .exe)
生成命令:gcc hello.c -o hello(或 gcc hello.o -o hello)
内容:完整的、可直接运行的程序
特点:可以直接 ./hello 运行
现在有如下C语言代码
程序文件hello.c:
#include <stdio.h>
// 自定义函数(供 main.c 调用)
void hello_func() {
printf("Hello, 多文件编译!\n");
}
程序文件math.c(这里不写math.h,你知道有,能调用就好):
// 加法函数实现(被 main.c 调用)
int add(int a, int b) {
return a + b;
}
主程序文件main.c(调用 math.c 和 hello.c 的功能):
#include <stdio.h>
#include "math.h" // 包含自定义头文件(对应 math.c)
extern void hello_func(); // 外部函数声明(来自 hello.c)
int main() {
hello_func(); // 调用 hello.c 中的函数
int x = 10, y = 20;
printf("10 + 20 = %d\n", add(x, y)); // 调用 math.c 中的函数
return 0;
}

流程如上图说明:
单文件编译链(hello 系列):hello.c 经预处理生成 hello.i,再经编译生成汇编文件 hello.s,接着经汇编生成目标文件 hello.o,最后经链接生成可执行文件 hello。
多文件编译链(main、math 系列):main.c 和 math.c 各自经历预处理、编译、汇编过程,生成 main.o 和 math.o,再将这些目标文件与 hello.o 一起链接,最终生成可执行文件 program。
常见误区澄清
.o 文件 = 可执行文件(❌ )
.o 文件是“半成品”,需要链接才能运行(√)
.so 文件 = 可执行文件(❌ )
.so 是库文件,不能直接运行,只在运行时被其他程序加载(√)
静态库 .a 和动态库 .so 只是格式不同(❌ )
.a:链接时复制代码进可执行文件(√)
.so:运行时动态加载,可执行文件只存引用(√)
文件类型速查表
| 文件扩展名 | 中文名 | 生成阶段 | 作用 | 是否可执行 |
| .c | 源文件 | 手写 | 人类代码 | ❌ |
| .i | 预处理文件 | 预处理 | 展开宏和头文件 | ❌ |
| .s | 汇编文件 | 编译 | CPU指令文本表示 | ❌ |
| .o | 目标文件 | 汇编 | 二进制机器码(未链接) | ❌ |
| .a | 静态库 | 打包 | 多个.o打包 | ❌ |
| .so | 动态库 | 编译 | 可共享的库代码 | ❌ |
| 无扩展名 | 可执行文件 | 链接 | 完整程序 | √ |
2239






