GCC 编译器选项详解:从总体选项到链接器与目录选项
GCC 编译器选项详解:从总体选项到链接器与目录选项
本文是 GCC 编译器系列的进阶篇,详细介绍 GCC 的各种编译选项,包括总体选项(-c、-S、-E、-o、-v)、警告选项(-Wall)、调试选项(-g)、优化选项(-O0~-O3)、链接器选项(-l、-static、-shared、-nostdlib 等)以及目录选项(-I、-L、-B)。通过大量示例和对比,帮助你真正理解每个选项的作用,避免在实际开发中踩坑。
文章目录
1. 准备工作:示例代码
为了演示这些选项,我们先创建三个简单的文件:main.c、sub.c 和 sub.h。
main.c
c
#include <stdio.h>
#include "sub.h"
int main(int argc, char *argv[])
{
int i; // 这个变量定义后从未使用,用来演示 -Wall 警告
printf("Main fun!\n");
sub_fun();
return 0;
}
sub.h
c
void sub_fun(void);
sub.c
c
#include <stdio.h>
void sub_fun(void)
{
printf("Sub fun!\n");
}
这三个文件将作为我们编译实验的基础。
2. GCC 总体选项(Overall Options)
总体选项控制 GCC 在编译过程的哪个阶段停止,以及如何指定输出文件。
2.1 -c – 只编译不链接
作用:对源文件进行预处理、编译、汇编,生成目标文件(.o 文件),但不进行链接。
典型用法:
bash
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
-o main.o指定输出的目标文件名。如果不指定,默认会把main.c编译成main.o(把.c后缀替换成.o)。- 生成的目标文件是二进制机器码,但其中的函数地址尚未确定(例如
printf的地址还是空缺),需要链接后才能运行。
为什么需要 -c:在大型项目中,我们通常分别编译每个 .c 文件,最后再统一链接。这样修改一个文件后,只需重新编译那个文件,可以大大节省时间。
2.2 -S – 只编译不汇编
作用:进行预处理和编译,生成汇编代码文件(.s 文件),不进行汇编和链接。
典型用法:
bash
gcc -S -o main.s main.c
生成的 main.s 是文本格式的汇编代码,你可以用文本编辑器打开查看。这对于理解编译器如何将 C 代码转换为汇编非常有帮助。
2.3 -E – 只预处理不编译
作用:仅执行预处理(展开宏、包含头文件、处理条件编译),将结果输出到标准输出(屏幕),不编译、不汇编、不链接。
典型用法:
bash
gcc -E main.c
如果想把预处理结果保存到文件:
bash
gcc -E -o main.i main.c
实用技巧:当你想确认某个宏是否被正确定义,或者头文件被哪个路径包含时,可以用 -E 查看。例如:
bash
gcc -E -dM main.c | grep "printf"
-dM 选项会列出所有宏定义,结合 -E 可以快速查看所有宏。
2.4 -o file – 指定输出文件名
作用:无论 GCC 处于哪个阶段(预处理、编译、汇编、链接),-o 都可以用来指定输出文件的名称。
默认行为(不使用 -o 时):
- 链接生成的可执行文件默认名为
a.out。 - 编译生成的目标文件(
-c)默认将源文件后缀.c、.i、.s替换为.o。 - 汇编文件(
-S)默认替换为.s。 - 预处理(
-E)默认输出到屏幕。
示例:
bash
gcc main.c # 生成 a.out
gcc -o myapp main.c # 生成 myapp
gcc -c -o myobj.o main.c # 生成 myobj.o
2.5 -v – 显示详细的编译过程
作用:输出 GCC 的内部执行细节,包括:
- 配置信息(GCC 是如何编译出来的)
- 调用的子程序(如
cc1、as、collect2、ld)及其参数 - 搜索路径(头文件、库文件)
典型用法:
bash
gcc -v -o test main.c sub.c
输出会很长,但你可以从中看到:
cc1是真正的 C 编译器(预处理+编译)as是汇编器collect2或ld是链接器- 链接时默认加入了哪些启动文件(
crt1.o、crti.o等)和库文件(-lc、-lgcc等)
实用技巧:在交叉编译时,使用 arm-linux-gnueabihf-gcc -v 可以查看交叉编译器的默认头文件路径和库路径。
3. 警告选项(Warning Option):-Wall
作用:打开几乎所有常见的编译警告。编译器会提示你可能存在问题的代码,但不会影响生成目标文件。
示例:我们的 main.c 中定义了一个从未使用的变量 i。如果不加 -Wall,编译时不会有任何提示:
bash
gcc -c -o main.o main.c # 没有任何输出
加上 -Wall 后:
bash
gcc -Wall -c -o main.o main.c
输出:
text
main.c: In function 'main':
main.c:6:9: warning: unused variable 'i' [-Wunused-variable]
int i;
^
💡 最佳实践:在开发阶段,强烈建议总是加上
-Wall(甚至可以加上-Wextra和-Werror),尽早发现潜在问题。很多难以定位的 bug 其实就是一个小小的警告导致的。
4. 调试选项(Debugging Option):-g
作用:在生成的目标文件或可执行文件中嵌入调试信息(如变量名、行号、函数名等),供调试器(如 GDB)使用。
典型用法:
bash
gcc -g -c -o main.o main.c
gcc -g -o test main.o sub.o
调试信息的格式可以是 stabs、COFF、DWARF 等,不同系统默认不同。对于 Linux,通常使用 DWARF。GDB 可以自动识别。
注意:
- 添加
-g会使可执行文件变大,但不会影响程序运行逻辑。 - 发布正式版本时,通常不加
-g(或者生成单独的调试符号文件)。 -g和优化选项(如-O2)可以同时使用,但优化后的代码在调试时可能会跳来跳去(因为代码被重排了),调试体验较差。
5. 优化选项(Optimization Options)
GCC 提供了多个级别的优化,用 -O(大写字母 O)后跟数字表示。优化级别越高,编译时间越长,生成的代码运行速度越快(或者体积越小),但调试越困难。
5.1 各优化级别对比
| 选项 | 含义 | 特点 |
|---|---|---|
-O0 |
不优化(默认) | 编译最快,代码最易调试,每条 C 语句都对应清晰的机器码 |
-O1 或 -O |
基本优化 | 尝试减少代码大小和执行时间,不进行空间换时间的优化 |
-O2 |
进一步优化 | 打开几乎所有不涉及空间换时间的优化(如指令调度、跳转线程等),推荐用于正式发布 |
-O3 |
更激进优化 | 在 -O2 基础上增加函数内联(-finline-functions)、循环展开等,可能导致代码体积变大 |
-Os |
优化代码大小 | 在 -O2 基础上,执行不增加代码大小的优化,适合嵌入式系统 |
示例:使用 -O2 编译
bash
gcc -O2 -c -o main.o main.c
gcc -O2 -c -o sub.o sub.c
gcc -O2 -o test main.o sub.o
🧠 建议:开发调试阶段用
-O0或不用-O,正式发布用-O2。除非明确需要,尽量避免-O3,因为它可能引入难以察觉的 bug(例如浮点运算顺序改变)。
5.2 多个 -O 选项的处理
如果指定了多个 -O 选项,最后一个生效:
bash
gcc -O1 -O3 -c main.c # 实际使用 -O3
6. 链接器选项(Linker Options)
链接器(ld 或 collect2)负责将多个目标文件(.o)和库文件组合成可执行文件。GCC 可以通过以下选项影响链接过程。
6.1 object-file-name – 直接指定目标文件
作用:在命令行中直接写上目标文件名(以 .o 结尾)或库文件名(以 .a 结尾),GCC 会将其传递给链接器。
示例:
bash
gcc -o test main.o sub.o
这里 main.o 和 sub.o 就是目标文件。
6.2 -llibrary – 链接指定的库
作用:链接名为 library 的库文件。GCC 会自动在库名前加上 lib,后缀加上 .a(静态库)或 .so(动态库),并在标准库路径(以及 -L 指定的路径)中搜索。
示例:链接数学库 libm.so
bash
gcc -o calc calc.c -lm
-lm表示链接libm.so或libm.a。
注意:
-l选项的顺序很重要。如果a.o调用了b.o中的函数,那么-la应该出现在-lb之前。链接器按顺序处理,默认只向前查找未定义的符号。- 可以使用
-L指定额外的库搜索路径(见目录选项)。
6.3 -static – 静态链接
作用:阻止使用共享库(动态库),强制将所有库的代码复制到最终的可执行文件中。
对比:
bash
gcc -o test_dynamic main.o sub.o # 动态链接
gcc -o test_static main.o sub.o -static # 静态链接
ls -lh test_*
输出示例:
text
-rwxr-xr-x 1 book book 6.5K test_dynamic
-rwxr-xr-x 1 book book 534K test_static
静态链接的文件体积大很多,但可以独立运行,不需要目标系统上有对应的动态库。
适用场景:
- 目标系统缺少动态库或版本不匹配
- 嵌入式系统或 initramfs 中不希望依赖外部库
6.4 -shared – 生成共享库(动态库)
作用:生成一个共享目标文件(.so),可以被其他程序动态加载。
示例:制作 libsub.so
bash
gcc -c -fPIC -o sub.o sub.c # 生成位置无关代码
gcc -shared -o libsub.so sub.o # 生成动态库
使用动态库编译程序:
bash
gcc -o test main.o -L. -lsub
运行时需要确保 libsub.so 在动态链接器的搜索路径中(如 /usr/lib 或通过 LD_LIBRARY_PATH 指定)。
6.5 -nostartfiles – 不链接启动文件
作用:不链接系统标准的启动文件(如 crt1.o、crti.o、crtend.o、crtn.o),但仍然链接标准库。
示例:
bash
gcc -nostartfiles -o test main.o sub.o
链接时会警告找不到入口符号 _start(因为启动文件通常定义了 _start,它负责调用 main)。编译出来的程序无法正常运行,这个选项通常用于编译操作系统内核或 bootloader,这些程序自己定义入口点。
6.6 -nostdlib – 不链接启动文件和标准库
作用:既不链接启动文件,也不链接任何标准库(如 libc)。
示例:
bash
gcc -nostdlib -o test main.o sub.o
会产生大量未定义引用错误(因为 printf 在 libc 中)。这个选项常用于裸机程序、内核或bootloader,它们自己实现所有需要的函数。
6.7 -Xlinker option – 直接传递选项给链接器
作用:将 option 原样传递给链接器。如果需要传递带参数的选项,需要写两次 -Xlinker。
示例:传递 -assert definitions 给链接器
bash
gcc -o test main.o sub.o -Xlinker -assert -Xlinker definitions
6.8 -Wl,option – 更简洁地传递选项给链接器
作用:将 option 传递给链接器,多个选项可以用逗号分隔。
示例:传递 -Map=output.map(生成链接映射文件)
bash
gcc -o test main.o sub.o -Wl,-Map=output.map
这是最常用的方式,比 -Xlinker 更简洁。
6.9 -u symbol – 强制链接未使用的符号
作用:让链接器认为 symbol 是一个未定义符号,从而强制从库中拉入定义该符号的目标文件。
示例:假设静态库 libfoo.a 中有函数 foo_init,但程序中没有直接调用它,你想把它也链接进来:
bash
gcc -o test main.o -lfoo -u foo_init
7. 目录选项(Directory Options)
这些选项用于指定头文件和库文件的搜索路径。
7.1 -Idir – 添加头文件搜索路径
作用:在头文件的搜索路径列表中添加 dir 目录。
搜索规则:
- 对于
#include <file>:只搜索系统标准目录 +-I指定的目录。 - 对于
#include "file":先从当前文件所在目录搜索,再从-I指定的目录和系统标准目录搜索。
示例:
bash
gcc -c -I /my/include -o main.o main.c
💡 如果有多个
-I目录,先出现的目录优先搜索。
7.2 -I- – 控制搜索路径的分隔
作用:将 -I 目录分为两组:
- 在
-I-之前的-I目录只用于搜索#include "file"(用户头文件)。 - 在
-I-之后的-I目录用于搜索所有#include(包括<>和"")。
此外,-I- 还会禁止当前目录成为 #include "file" 的第一搜索位置。
这个选项在现代 GCC 中已经不太常用,更推荐使用 -iquote、-isystem 等更细粒度的选项。但了解它有助于理解旧代码。
7.3 -Ldir – 添加库文件搜索路径
作用:在 -l 的搜索路径列表中添加 dir 目录。链接器会在这些目录中查找 libname.a 或 libname.so。
示例:
bash
gcc -o test main.o -L. -lsub
这条命令会在当前目录(.)中搜索 libsub.so 或 libsub.a。
注意:-L 只影响链接阶段,不影响编译阶段。
7.4 -Bprefix – 指定工具前缀
作用:指定 GCC 在调用子工具(如 cc1、as、ld)时使用的前缀。这个前缀可以是目录路径,也可以是文件名前缀。
示例:使用 /usr/local/my-gcc-tools/ 下的工具
bash
gcc -B/usr/local/my-gcc-tools/ -c main.c
GCC 会尝试执行 /usr/local/my-gcc-tools/cc1、/usr/local/my-gcc-tools/as 等。
环境变量:也可以通过 GCC_EXEC_PREFIX 环境变量设置类似效果。-B 优先级更高。
这个选项在交叉编译时可能用到,但通常我们直接使用完整的交叉编译工具链名称(如 arm-linux-gnueabihf-gcc)即可,不需要手动 -B。
8. 其他工具:ld、objdump、objcopy 简介
在开发普通应用程序时,我们很少直接调用 ld(链接器)、objdump(显示目标文件信息)、objcopy(复制和转换目标文件)。但在裸机开发、bootloader、内核移植或调试时,这些工具非常有用。
-
ld:链接器,将目标文件和库文件组合成可执行文件。GCC 在内部会调用它。 -
objdump:反汇编、查看目标文件的段信息、符号表等。例如:bash
objdump -d test # 反汇编 text 段 objdump -t test # 显示符号表 -
objcopy:转换目标文件格式,例如将 ELF 文件转换为纯二进制文件(用于烧录到 Flash):bash
arm-linux-objcopy -O binary test test.bin
这些工具的具体用法将在裸机或内核相关章节详细讲解,此处只需知道它们的存在。
9. 综合示例:从源码到可执行文件的完整流程
我们用前面三个文件演示一个完整的、带优化的编译链接过程。
bash
# 1. 分别编译,生成目标文件(使用 -O2 优化,-Wall 警告,-g 调试信息)
gcc -Wall -g -O2 -c -o main.o main.c
gcc -Wall -g -O2 -c -o sub.o sub.c
# 2. 链接生成动态链接的可执行文件(默认)
gcc -o test_dyn main.o sub.o
# 3. 链接生成静态链接的可执行文件
gcc -static -o test_static main.o sub.o
# 4. 制作动态库 libsub.so
gcc -fPIC -shared -o libsub.so sub.c
# 5. 使用动态库编译(假设 libsub.so 在当前目录)
gcc -o test_use_dyn main.o -L. -lsub
# 6. 查看生成的文件大小
ls -lh test_* libsub.so
10. 总结速查表
| 选项 | 作用 | 示例 |
|---|---|---|
-c |
只编译不链接,生成 .o |
gcc -c main.c |
-S |
生成汇编代码 .s |
gcc -S main.c |
-E |
只预处理,输出到屏幕 | gcc -E main.c |
-o file |
指定输出文件名 | gcc -o myapp main.c |
-v |
显示详细编译过程 | gcc -v main.c |
-Wall |
打开常见警告 | gcc -Wall main.c |
-g |
添加调试信息 | gcc -g main.c |
-O0, -O1, -O2, -O3 |
优化级别 | gcc -O2 main.c |
-llibrary |
链接库 liblibrary.a/.so |
gcc main.c -lm |
-static |
静态链接 | gcc -static main.c |
-shared |
生成动态库 | gcc -shared -fPIC -o lib.so sub.c |
-nostartfiles |
不链接启动文件 | gcc -nostartfiles ... |
-nostdlib |
不链接启动文件和标准库 | gcc -nostdlib ... |
-Wl,option |
传递选项给链接器 | gcc -Wl,-Map=output.map |
-Idir |
添加头文件搜索路径 | gcc -I ./inc main.c |
-Ldir |
添加库文件搜索路径 | gcc -L./lib main.c -lsub |
-Bprefix |
指定工具前缀 | gcc -B/my-tools/ main.c |
🎉 结束语:GCC 的选项非常丰富,但本文介绍的这些已经覆盖了日常开发的绝大部分需求。掌握它们,你就能自如地控制编译的每个阶段、优化级别、链接行为以及目录搜索。记住:多动手试,用
-v看看内部发生了什么,你会理解得更深刻。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)