目录
1. 编译的四个阶段(总览)
2. 一步步手动体验(推荐亲手试)
2.1 预处理(-E)
2.2 编译(-S)
2.3 汇编(-c)
2.4 链接(-o)
3. 常用编译选项详解
4. 多文件编译的两种方式
方式一:一步到位
方式二:分开编译,统一链接(推荐大型项目)
5. 动态库(共享库)的制作与使用
5.1 制作动态库
5.2 使用动态库
5.3 运行程序
6. 静态库的制作与使用
6.1 制作静态库
6.2 使用静态库
7. 很有用的附加选项(调试与查错)
8. 交叉编译说明
9. 总结
欢迎来到 GCC 编译器的世界!如果你刚开始接触 Linux 下的 C/C++ 开发,可能会对一堆编译选项感到困惑。别担心,本文将从零开始,把 GCC 的编译过程拆成最细的步骤,每一步都配上通俗的解释和实际命令,让你不仅理解,还能自己动手操作。以后遇到编译相关的问题,也可以随时回来查阅。
1. 编译的四个阶段(总览)
一个 C/C++ 源文件变成可执行文件,需要经过预处理、编译、汇编和链接四个步骤。每个步骤都会生成中间文件,直到最后得到可以直接运行的程序。
下面的流程图可以帮你建立直观印象:
text .c / .cpp (源代码) │ │ 预处理 (Preprocessing) ▼ .i (预处理后的文件) │ │ 编译 (Compilation) ▼ .s (汇编代码文件) │ │ 汇编 (Assembly) ▼ .o (目标文件,机器码但不可独立运行) │ │ 链接 (Linking) + 其他目标文件/库 ▼ 可执行文件 (例如 hello, a.out)每个阶段对应的 GCC 选项:
| 阶段 | 选项 | 输入 | 输出 | 示例命令 |
|---|---|---|---|---|
| 预处理 | -E | hello.c | hello.i | gcc -E -o hello.i hello.c |
| 编译 | -S | hello.i | hello.s | gcc -S -o hello.s hello.i |
| 汇编 | -c | hello.s | hello.o | gcc -c -o hello.o hello.s |
| 链接 | 无选项(或-o) | hello.o | hello | gcc -o hello hello.o |
注意:-c选项实际上是“预处理 + 编译 + 汇编”三步的合并,但不链接。而直接用gcc hello.c会完成所有四个步骤,默认生成a.out。
2. 一步步手动体验(推荐亲手试)
让我们从一个最简单的hello.c文件开始,手动执行每一个步骤,看看中间文件都长什么样。
c // hello.c #include <stdio.h> #define GREETING "Hello, world!" int main() { printf("%s\n", GREETING); return 0; }2.1 预处理(-E)
预处理会做三件事:头文件展开、宏替换、删除注释。执行:
bash gcc -E -o hello.i hello.c现在打开hello.i,你会看到#include <stdio.h>被替换成了几百行代码,GREETING被替换成了字符串。这个文件仍然是文本,但已经没有了宏和 #include。
2.2 编译(-S)
编译将预处理后的.i文件翻译成汇编代码(人类可读的汇编语言)。执行:
bash gcc -S -o hello.s hello.i打开hello.s,你会看到类似movl,pushq这样的指令,这是针对你当前 CPU 架构的汇编代码。
2.3 汇编(-c)
汇编器将汇编代码转换成机器码(二进制),生成目标文件(.o或.obj)。目标文件已经是二进制了,但还不能直接运行,因为它可能还依赖其他函数(比如printf)。执行:
bash gcc -c -o hello.o hello.s现在hello.o是一个二进制文件,可以用file hello.o查看文件类型。
2.4 链接(-o)
链接器将目标文件和所需的库(比如 C 标准库)合并,生成最终的可执行文件。执行:
bash gcc -o hello hello.o(也可以省略-o hello,默认生成a.out)。运行它:
bash ./hello输出:Hello, world!
3. 常用编译选项详解
下面列出最实用的选项,每个都会配一个使用场景。
| 选项 | 含义 | 典型用法 |
|---|---|---|
| -E | 只进行预处理,不编译、汇编、链接 | 想查看宏展开结果:gcc -E -dM hello.c > macros.txt |
| -c | 执行预处理、编译、汇编,但不链接 | 生成目标文件,用于后续单独链接:gcc -c -o main.o main.c |
| -o <file> | 指定输出文件名 | 默认输出是a.out,用-o改名:gcc -o myprog main.c |
| -I <dir> | 添加头文件搜索目录 | 当#include "myheader.h"不在标准路径时:gcc -I../include -c main.c |
| -L <dir> | 添加库文件搜索目录 | 链接时告诉链接器去哪个目录找库:gcc -o test main.o -L./libs -lmylib |
| -l<name> | 链接名为lib<name>.so或lib<name>.a的库 | 链接数学库:-lm;链接自己建的库:-lmylib |
| -static | 强制静态链接 | 生成的可执行文件不依赖动态库,体积大但移植性好:gcc -static -o hello hello.c |
| -v | 显示详细的编译过程(调试用) | 可以看到实际调用的子程序(cc1、as、collect2):gcc -v -o hello hello.c |
4. 多文件编译的两种方式
假设你有两个源文件:main.c和sub.c,想生成test可执行文件。
方式一:一步到位
bash gcc -o test main.c sub.cGCC 会自动完成所有步骤,生成最终可执行文件test。
方式二:分开编译,统一链接(推荐大型项目)
bash # 分别编译成目标文件 gcc -c -o main.o main.c gcc -c -o sub.o sub.c # 链接目标文件 gcc -o test main.o sub.o这种方式的好处是:如果只修改了main.c,只需重新编译main.o,再链接一次即可,节省时间。
5. 动态库(共享库)的制作与使用
动态库在程序运行时才被加载,多个程序可以共享一份库代码,节省内存。
5.1 制作动态库
假设有sub.c、sub2.c、sub3.c,你想把它们打包成一个动态库libsub.so。
bash # 先编译成目标文件(注意:-fPIC 是位置无关代码,动态库必需) gcc -c -fPIC -o sub.o sub.c gcc -c -fPIC -o sub2.o sub2.c gcc -c -fPIC -o sub3.o sub3.c # 用 -shared 生成动态库 gcc -shared -o libsub.so sub.o sub2.o sub3.o5.2 使用动态库
假设你的主程序main.c调用了库中的函数,编译时:
bash gcc -c -o main.o main.c gcc -o test main.o -L. -lsub-L.告诉链接器在当前目录(.)查找库文件。-lsub告诉链接器链接libsub.so(lib前缀和.so后缀自动补全)。
5.3 运行程序
动态库在程序运行时必须能被找到。有三种常见方法:
将库复制到系统标准目录(如
/lib或/usr/lib):bash sudo cp libsub.so /lib ./test临时设置环境变量
LD_LIBRARY_PATH:bash export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./test将库路径写入可执行文件(使用
-rpath链接选项,这里不展开)。
6. 静态库的制作与使用
静态库在链接时会被直接整合到可执行文件中,运行时不再需要库文件。
6.1 制作静态库
静态库本质上是多个.o文件的归档,使用ar工具创建。
bash # 先编译目标文件(静态库不需要 -fPIC) gcc -c -o sub.o sub.c gcc -c -o sub2.o sub2.c gcc -c -o sub3.o sub3.c # 用 ar 打包成静态库 libsub.a ar crs libsub.a sub.o sub2.o sub3.o6.2 使用静态库
bash gcc -c -o main.o main.c gcc -o test main.o libsub.a或者用-l和-L选项(链接器会自动在-L路径下找libsub.a):
bash gcc -o test main.o -L. -lsub 运行程序时不需要额外的库,直接 ./test 即可。7. 很有用的附加选项(调试与查错)
以下选项在开发中经常用来检查问题或生成辅助文件:
| 选项 | 作用 |
|---|---|
gcc -E main.c | 只预处理并输出到屏幕,可以快速查看宏展开后的代码。 |
gcc -E -dM main.c > 1.txt | 列出文件中定义的所有宏(包括系统宏),输出到文件。 |
gcc -Wp,-MD,abc.dep -c -o main.o main.c | 生成依赖文件abc.dep(Makefile 中常用,记录头文件依赖)。 |
echo 'main(){}' | gcc -E -v - | 查看默认的头文件搜索路径、库搜索路径(非常有用)。 |
gcc -v -o test main.c | 显示完整的编译过程,包括调用的子程序。 |
8. 交叉编译说明
如果你的开发环境是 x86 PC,但目标机器是 ARM(例如树莓派、嵌入式设备),你需要使用交叉编译工具链,例如arm-linux-gnueabihf-gcc。
用法和普通 GCC 完全一样,只是命令前缀不同:
bash arm-linux-gnueabihf-gcc -o hello hello.c # 编译 ARM 版本的可执行文件 arm-linux-gnueabihf-gcc -c -fPIC -o sub.o sub.c # 编译 ARM 的动态库目标文件交叉编译时,特别注意头文件和库文件也必须是对应 ARM 架构的,通常通过-I、-L指定交叉编译环境的 sysroot。
9. 总结
现在你应该对 GCC 的编译流程和常用选项有了清晰的认识。记住:
四步走:预处理(
-E)、编译(-S)、汇编(-c)、链接(无选项)。多文件:可一次编译,也可分开编译再链接。
库:动态库(
.so)运行时需要;静态库(.a)直接嵌入可执行文件。查错:
-v、-E -dM、echo ... | gcc -E -v -能帮你看到编译器的内部行为。
遇到问题就回来翻一翻,对照着命令行尝试,很快就能熟练掌握。祝编译顺利!