一、 代码是如何变成程序的?
在 Linux 下,我们习惯用 gcc hello.c -o hello 一键编译。但这背后其实经历了四个极其清晰的阶段(面试高频考点):
-
预处理(
-E) :展开头文件、宏替换、去注释。生成.i文件。 -
编译(
-S) :检查语法,将 C 代码翻译成汇编语言。生成.s文件。 -
汇编(
-c) :将汇编代码翻译成机器指令,生成二进制的目标文件.o。 -
链接:将目标文件与库文件结合起来,生成最终的可执行文件。
二、 动态链接 vs 静态链接
如果程序用到了标准库函数(如 printf),链接阶段就要把库连进来。
-
静态链接(
.a):把库代码直接拷贝进可执行文件。优点是运行不依赖环境;缺点是文件大、占内存、更新困难。 -
动态链接(
.so) :程序运行时才去加载共享库。优点是文件小、省内存;缺点是环境缺失会导致无法运行。Linux 下 GCC 默认使用动态链接。
三、 实战:用指令拆解编译过程
bash
# 1. 预处理
gcc -E hello.c -o hello.i
# 2. 编译(生成汇编)
gcc -S hello.i -o hello.s
# 3. 汇编(生成二进制目标文件)
gcc -c hello.s -o hello.o
# 4. 链接
gcc hello.o -o hello
四、 实用工具:file 与 ldd
-
file hello:可以查看文件类型,验证它是一个dynamically linked(动态链接)的可执行文件。 -
ldd hello:列出该程序依赖的所有动态库(如libc.so.6),排查库缺失问题时的利器。
总结
以前只知道敲 gcc,现在明白了"预处理 -> 编译 -> 汇编 -> 链接"的底层逻辑。这不仅是语法,更是操作系统运行程序的基石。
自我实战操作
在云服务器上,你需要先确认装了 gcc。如果没装,执行 sudo apt install -y gcc。
实战 1:剥洋葱式看编译过程
bash
cd ~/linux_study
mkdir gcc_test
cd gcc_test
vim hello.c
(在 vim 里写一段简单代码,按 Esc 后敲 :wq 保存)
cpp
#include <stdio.h>
#define PI 3.14
int main() {
printf("Hello GCC! PI = %f\n", PI);
return 0;
}
接下来,我们一步步"拆解"它:
bash
# 1. 预处理:把展开头文件、替换宏(把 PI 换成 3.14)
gcc -E hello.c -o hello.i
# 用 vim 或者 cat 看看 hello.i,你会发现代码变得极长,PI 全变成了 3.14
# 2. 编译:变成汇编代码
gcc -S hello.i -o hello.s
# 用 vim 看看 hello.s,你能看到类似 mov, push 这种汇编指令
# 3. 汇编:变成二进制目标文件(无法直接 cat 看,会乱码)
gcc -c hello.s -o hello.o
# 4. 链接:生成最终可执行文件
gcc hello.o -o hello
# 5. 运行
./hello


实战 2:验证动态链接(装逼神技)
bash
# 查看 hello 这个程序到底是什么类型的文件
file hello
# 查看 hello 依赖了哪些动态库(会看到 libc.so.6,这就是 C 标准库)
ldd hello
# 看看 hello.o 和 hello 的体积差异(链接后变大了,因为引入了库信息)
ls -l hello.o hello
