目录
[1. 编译的四个阶段(总览)](#1. 编译的四个阶段(总览))
[2. 一步步手动体验(推荐亲手试)](#2. 一步步手动体验(推荐亲手试))
[2.1 预处理(-E)](#2.1 预处理(-E))
[2.2 编译(-S)](#2.2 编译(-S))
[2.3 汇编(-c)](#2.3 汇编(-c))
[2.4 链接(-o)](#2.4 链接(-o))
[3. 常用编译选项详解](#3. 常用编译选项详解)
[4. 多文件编译的两种方式](#4. 多文件编译的两种方式)
[5. 动态库(共享库)的制作与使用](#5. 动态库(共享库)的制作与使用)
[5.1 制作动态库](#5.1 制作动态库)
[5.2 使用动态库](#5.2 使用动态库)
[5.3 运行程序](#5.3 运行程序)
[6. 静态库的制作与使用](#6. 静态库的制作与使用)
[6.1 制作静态库](#6.1 制作静态库)
[6.2 使用静态库](#6.2 使用静态库)
[7. 很有用的附加选项(调试与查错)](#7. 很有用的附加选项(调试与查错))
[8. 交叉编译说明](#8. 交叉编译说明)
[9. 总结](#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 文件开始,手动执行每一个步骤,看看中间文件都长什么样。
cs
c
// hello.c
#include <stdio.h>
#define GREETING "Hello, world!"
int main() {
printf("%s\n", GREETING);
return 0;
}
2.1 预处理(-E)
预处理会做三件事:头文件展开 、宏替换 、删除注释。执行:
bash
bash
gcc -E -o hello.i hello.c
现在打开 hello.i,你会看到 #include <stdio.h> 被替换成了几百行代码,GREETING 被替换成了字符串。这个文件仍然是文本,但已经没有了宏和 #include。
2.2 编译(-S)
编译将预处理后的 .i 文件翻译成汇编代码(人类可读的汇编语言)。执行:
bash
bash
gcc -S -o hello.s hello.i
打开 hello.s,你会看到类似 movl, pushq 这样的指令,这是针对你当前 CPU 架构的汇编代码。
2.3 汇编(-c)
汇编器将汇编代码转换成机器码 (二进制),生成目标文件 (.o 或 .obj)。目标文件已经是二进制了,但还不能直接运行,因为它可能还依赖其他函数(比如 printf)。执行:
bash
bash
gcc -c -o hello.o hello.s
现在 hello.o 是一个二进制文件,可以用 file hello.o 查看文件类型。
2.4 链接(-o)
链接器将目标文件和所需的库(比如 C 标准库)合并,生成最终的可执行文件。执行:
bash
bash
gcc -o hello hello.o
(也可以省略 -o hello,默认生成 a.out)。运行它:
bash
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
bash
gcc -o test main.c sub.c
GCC 会自动完成所有步骤,生成最终可执行文件 test。
方式二:分开编译,统一链接(推荐大型项目)
bash
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
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.o
5.2 使用动态库
假设你的主程序 main.c 调用了库中的函数,编译时:
bash
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):bashbash sudo cp libsub.so /lib ./test -
临时设置环境变量
LD_LIBRARY_PATH:bashbash export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./test -
将库路径写入可执行文件 (使用
-rpath链接选项,这里不展开)。
6. 静态库的制作与使用
静态库在链接时会被直接整合到可执行文件中,运行时不再需要库文件。
6.1 制作静态库
静态库本质上是多个 .o 文件的归档,使用 ar 工具创建。
bash
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.o
6.2 使用静态库
bash
bash
gcc -c -o main.o main.c
gcc -o test main.o libsub.a
或者用 -l 和 -L 选项(链接器会自动在 -L 路径下找 libsub.a):
bash
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
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 -能帮你看到编译器的内部行为。
遇到问题就回来翻一翻,对照着命令行尝试,很快就能熟练掌握。祝编译顺利!