04 - 常用编译选项
04 - 常用编译选项
全面掌握 GCC 最常用的编译选项——优化、警告、标准选择、路径指定、预定义宏等。
4.1 选项分类概览
GCC 选项数量庞大(数百个),但日常开发中常用的约 30-50 个。按功能可分为以下几类:
| 类别 | 常用选项 | 说明 |
|---|---|---|
| 输出控制 | -o, -E, -S, -c | 控制输出文件名和编译阶段 |
| 优化 | -O0, -O1, -O2, -O3, -Os | 控制优化级别 |
| 警告 | -Wall, -Wextra, -Werror, -Wpedantic | 控制警告信息 |
| 标准选择 | -std=c11, -std=c++17 | 选择语言标准 |
| 调试 | -g, -g1, -g2, -g3 | 生成调试信息 |
| 预定义 | -D, -U | 定义/取消宏 |
| 头文件路径 | -I, -isystem | 指定头文件搜索路径 |
| 库路径 | -L, -l | 指定库路径和库名称 |
| 代码生成 | -fPIC, -fPIE, -fno-common | 控制代码生成方式 |
| 架构 | -march, -mtune, -msse, -mavx | 目标架构和指令集 |
4.2 输出控制选项
# 指定输出文件名
gcc -o hello main.c # 输出 hello
# 不指定 -o 时的默认名
gcc main.c # 输出 a.out(可执行文件)
gcc -c main.c # 输出 main.o(目标文件)
gcc -S main.c # 输出 main.s(汇编文件)
gcc -E main.c # 输出到标准输出
gcc -E -o main.i main.c # 输出到文件
# 编译多个文件
gcc -o hello main.c greet.c # 一步完成
gcc -c main.c greet.c # 生成 main.o 和 greet.o
4.3 优化选项
| 选项 | 优化级别 | 说明 | 适用场景 |
|---|---|---|---|
-O0 | 无优化 | 默认值,编译最快,调试最准 | 开发调试 |
-O1 | 基本优化 | 减小代码大小和执行时间,不增加编译时间 | 一般开发 |
-O2 | 推荐优化 | 几乎所有安全的优化,推荐用于生产 | 生产构建 |
-O3 | 激进优化 | 在 -O2 基础上增加循环展开、向量化等 | 性能关键场景 |
-Os | 大小优化 | 优化代码大小,适合嵌入式和存储受限环境 | 嵌入式/容器 |
-Og | 调试友好优化 | 不影响调试的优化,兼顾性能和调试 | 日常开发 |
-Ofast | 极限优化 | -O3 加上可能违反标准的优化(如 -ffast-math) | 科学计算 |
# 不同优化级别的对比
for opt in O0 O1 O2 O3 Os Ofast; do
gcc -$opt -o hello_$opt main.c greet.c
size hello_$opt
done
# 输出示例:
# text data bss dec hex filename
# 1640 560 8 2208 8a0 hello_O0
# 1576 552 8 2136 858 hello_O1
# 1544 552 8 2104 838 hello_O2
# 1544 552 8 2104 838 hello_O3
# 1512 544 8 2064 810 hello_Os
# 1544 552 8 2104 838 hello_Ofast
-Og 的特殊地位
# -Og 是"日常开发"的最佳选择
gcc -g -Og -o hello main.c
# 为什么不推荐 -O0 + -g?
# -O0 完全不优化,某些变量优化后不存在,调试时体验差
# -Og 在不影响调试的前提下做基本优化,更贴近实际运行状态
-Ofast 的风险
# -Ofast 包含 -ffast-math,可能导致浮点计算结果不精确
# 例如: NaN 比较、无穷大处理可能不同
# 仅适用于对浮点精度要求不严格的科学计算场景
4.4 警告选项
# 启用常见警告(推荐始终使用)
gcc -Wall -o hello main.c
# 启用更多额外警告
gcc -Wall -Wextra -o hello main.c
# 将所有警告视为错误(CI/CD 中常用)
gcc -Wall -Wextra -Werror -o hello main.c
# 建议性的额外警告(严格标准遵从)
gcc -Wall -Wextra -Wpedantic -o hello main.c
常用警告选项详解
| 选项 | 说明 |
|---|---|
-Wall | 启用大部分常见警告(不是全部!) |
-Wextra | 启用 -Wall 未包含的额外警告 |
-Wpedantic | 严格遵循语言标准,对非标准扩展发出警告 |
-Werror | 将所有警告视为错误 |
-Werror=<warning> | 将特定警告视为错误 |
-Wno-<warning> | 禁用特定警告 |
-Wshadow | 变量遮蔽警告 |
-Wconversion | 隐式类型转换警告 |
-Wformat=2 | printf/scanf 格式字符串检查 |
-Wnull-dereference | 空指针解引用警告 |
-Wdouble-promotion | float 隐式提升为 double |
# 禁用特定警告
gcc -Wall -Wno-unused-variable -o hello main.c
# 将特定警告升级为错误
gcc -Wall -Werror=return-type -o hello main.c
4.5 语言标准选择
C 语言标准
# C89/C90
gcc -std=c89 -o hello main.c
gcc -ansi -o hello main.c # 等价于 -std=c89
# C99
gcc -std=c99 -o hello main.c
# C11
gcc -std=c11 -o hello main.c
# C17
gcc -std=c17 -o hello main.c
# C23(GCC 13+)
gcc -std=c23 -o hello main.c
# GNU 扩展模式(默认)
gcc -std=gnu17 -o hello main.c # C17 + GNU 扩展
# 查看默认标准
gcc -dM -E - < /dev/null | grep __STDC_VERSION__
# #define __STDC_VERSION__ 201710L (表示 C17)
C++ 标准
# C++11
g++ -std=c++11 -o hello main.cpp
# C++14
g++ -std=c++14 -o hello main.cpp
# C++17
g++ -std=c++17 -o hello main.cpp
# C++20
g++ -std=c++20 -o hello main.cpp
# C++23(GCC 13+)
g++ -std=c++23 -o hello main.cpp
# 查看默认标准
g++ -dM -E -x c++ /dev/null | grep __cplusplus
# #define __cplusplus 201703L (表示 C++17)
标准选择建议
| 场景 | 推荐标准 | 理由 |
|---|---|---|
| 新 C 项目 | -std=c17 | 成熟稳定,广泛支持 |
| 新 C++ 项目 | -std=c++17 | 主流选择,特性丰富 |
| 前沿 C++ | -std=c++20 / -std=c++23 | 协程、模块、格式化等新特性 |
| 兼容旧系统 | -std=c99 / -std=c++11 | 最广泛的兼容性 |
| Linux 内核 | -std=gnu11 | 内核需要 GNU 扩展 |
4.6 头文件路径选项
# 指定头文件搜索路径
gcc -I/usr/local/include -I./include -o hello main.c
# 多个路径可以连写
gcc -I./include -I../common/include -I/usr/local/include -o hello main.c
# 使用系统搜索路径(视为系统头文件,不产生警告)
gcc -isystem /usr/local/include -o hello main.c
# 使用引号搜索路径(对 #include "file.h" 的搜索顺序)
gcc -iquote ./include -o hello main.c
# 头文件搜索顺序(对于 #include <file.h>):
# 1. -I 指定的目录(按顺序)
# 2. -isystem 指定的目录
# 3. 系统默认目录(/usr/include 等)
# 头文件搜索顺序(对于 #include "file.h"):
# 1. 当前源文件所在目录
# 2. -iquote 指定的目录
# 3. -I 指定的目录
# 4. -isystem 指定的目录
# 5. 系统默认目录
查看搜索路径
# 查看默认头文件搜索路径
gcc -v -E -x c /dev/null 2>&1 | sed -n '/#include.*search starts/,/End of search/p'
# 或
cpp -v /dev/null 2>&1 | sed -n '/#include.*search/,/End of/p'
4.7 库路径与库链接
# 指定库搜索路径
gcc -L/usr/local/lib -L./lib -o hello main.c
# 链接特定库
# -l<name> 会搜索 lib<name>.so(动态库)或 lib<name>.a(静态库)
gcc -lm -lpthread -o hello main.c # 链接 libm 和 libpthread
gcc -L./lib -lmylib -o hello main.c # 链接 libmylib.so 或 libmylib.a
# 指定静态链接某个库
gcc -Wl,-Bstatic -lmylib -Wl,-Bdynamic -o hello main.c
# 链接时链接顺序很重要!被依赖的库放在后面
gcc main.o -lmylib -lm -o hello # ✅ 正确
gcc -lmylib -lm main.o -o hello # ❌ 可能报 undefined reference
# 查看链接的库
ldd hello
# 强制链接未使用的库
gcc -Wl,--no-as-needed -lmylib -o hello main.c
库链接顺序规则
链接顺序原则:
gcc main.o -lA -lB -lc
- main.o 中引用了 libA 中的符号
- libA 中引用了 libB 中的符号
- libB 中引用了 libc 中的符号
链接器从左到右扫描:
1. 扫描 main.o → 记录未解析符号
2. 扫描 libA → 解析 main.o 的符号,记录 libA 的未解析符号
3. 扫描 libB → 解析 libA 的符号,记录 libB 的未解析符号
4. 扫描 libc → 解析 libB 的符号
如果顺序反了:
gcc -lB -lA main.o
扫描 libB 时没有未解析符号,libB 的内容不会被链接
→ 链接器不会回溯搜索 → 报 undefined reference
4.8 预定义宏选项
# 定义宏(等价于源码中的 #define DEBUG 1)
gcc -DDEBUG=1 -o hello main.c
# 定义不带值的宏(等价于 #define DEBUG)
gcc -DDEBUG -o hello main.c
# 取消宏定义(等价于 #undef DEBUG)
gcc -UDEBUG -o hello main.c
# 常见用法
gcc -DVERSION=\"1.0.0\" -DBUILD_DATE=\"2026-05-10\" -o hello main.c
gcc -DNDEBUG -o hello main.c # 禁用 assert()
gcc -D_GNU_SOURCE -o hello main.c # 启用 GNU 扩展
在代码中使用
#include <stdio.h>
int main(void) {
#ifdef DEBUG
printf("Debug mode enabled\n");
#endif
printf("Version: %s\n", VERSION);
printf("Build date: %s\n", BUILD_DATE);
return 0;
}
gcc -DDEBUG -DVERSION=\"1.0.0\" -DBUILD_DATE=\"2026-05-10\" -o hello main.c
./hello
# Debug mode enabled
# Version: 1.0.0
# Build date: 2026-05-10
4.9 代码生成选项
| 选项 | 说明 |
|---|---|
-fPIC | 生成位置无关代码(Position Independent Code),用于共享库 |
-fPIE | 生成位置无关可执行文件,ASLR 安全加固(默认开启) |
-fno-common | 不允许未初始化的全局变量有多个定义(GCC 10+ 默认) |
-fcommon | 允许未初始化的全局变量有多个定义(GCC 9 及之前默认) |
-fpic | 类似 -fPIC,但对偏移量有限制(某些架构更高效) |
-fstack-protector | 启用栈保护(-fstack-protector-strong 推荐) |
-fno-omit-frame-pointer | 保留帧指针(便于调试和性能分析) |
-fasynchronous-unwind-tables | 生成异步展开表(默认开启,用于 backtrace) |
# 编译共享库
gcc -fPIC -shared -o libhello.so hello.c
# 栈保护(推荐生产使用)
gcc -fstack-protector-strong -o hello main.c
# 保留帧指针(便于 perf 分析)
gcc -fno-omit-frame-pointer -O2 -o hello main.c
4.10 架构相关选项
# 指定目标 CPU
gcc -march=native -o hello main.c # 本机 CPU(自动检测最优指令集)
gcc -march=x86-64-v3 -o hello main.c # x86-64-v3 微架构级别
# 调优目标
gcc -march=skylake -mtune=skylake -o hello main.c
# 指令集扩展
gcc -msse4.2 -o hello main.c # 启用 SSE 4.2
gcc -mavx2 -o hello main.c # 启用 AVX2
gcc -mavx512f -o hello main.c # 启用 AVX-512
# ARM 架构
aarch64-linux-gnu-gcc -march=armv8-a -o hello main.c
arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=neon -o hello main.c
x86-64 微架构级别
| 级别 | 特性要求 | 典型 CPU |
|---|---|---|
| x86-64 | 基线(SSE2) | 所有 x86-64 CPU |
| x86-64-v2 | SSE4.2, POPCNT, … | Intel Nehalem+, AMD Bulldozer+ |
| x86-64-v3 | AVX2, BMI2, … | Intel Haswell+, AMD Excavator+ |
| x86-64-v4 | AVX-512 | Intel Skylake-X+, AMD Zen 4+ |
4.11 常用编译选项速查表
开发阶段推荐选项
# 日常开发(调试友好 + 基本警告)
gcc -Wall -Wextra -g -Og -std=c17 -o hello main.c
# CI/CD(严格警告 + 所有警告视为错误)
gcc -Wall -Wextra -Wpedantic -Werror -std=c17 -o hello main.c
# 生产构建(优化 + 警告 + 安全加固)
gcc -Wall -Wextra -O2 -std=c17 \
-fstack-protector-strong \
-D_FORTIFY_SOURCE=2 \
-Wl,-z,relro,-z,now \
-o hello main.c
# 调试构建(调试信息 + 不优化)
gcc -Wall -g3 -O0 -fsanitize=address -o hello_debug main.c
# 性能分析
gcc -Wall -O2 -g -fno-omit-frame-pointer -pg -o hello_profile main.c
# 嵌入式/大小优化
gcc -Wall -Os -ffunction-sections -fdata-sections \
-Wl,--gc-sections -o hello_small main.c
Makefile 中的典型变量设置
CC = gcc
CFLAGS = -Wall -Wextra -std=c17
CFLAGS_DEBUG = -g3 -O0 -fsanitize=address
CFLAGS_RELEASE = -O2 -DNDEBUG
CFLAGS_CI = -Wall -Wextra -Wpedantic -Werror
LDFLAGS =
LDLIBS = -lm -lpthread
# 根据构建类型选择标志
ifdef DEBUG
CFLAGS += $(CFLAGS_DEBUG)
else
CFLAGS += $(CFLAGS_RELEASE)
endif
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
hello: main.o greet.o
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
要点回顾
| 要点 | 核心内容 |
|---|---|
| 优化级别 | -O0(调试) / -Og(开发) / -O2(生产) / -O3(性能) |
| 警告 | -Wall -Wextra 是最低要求,CI 中加 -Werror |
| 标准 | C: -std=c17,C++: -std=c++17 |
| 头文件 | -I 指定搜索路径,注意 "" 和 <> 的搜索顺序差异 |
| 库链接 | -L 指定路径,-l 指定库名,注意链接顺序 |
| 宏定义 | -D 定义,-U 取消 |
| 架构 | -march=native 针对本机优化,-march=x86-64-v2 保证兼容性 |
注意事项
不要使用
-w:-w禁用所有警告,这会隐藏潜在问题。始终使用-Wall -Wextra,对特定警告使用-Wno-<name>精确抑制。
-O2不等于-O1 + -O1: 每个优化级别是独立的,不存在叠加关系。
库链接顺序: 被依赖的库放在后面。如果遇到
undefined reference错误,先检查链接顺序。
-march=native不可移植: 使用-march=native编译的程序可能无法在不同 CPU 上运行。
扩展阅读
- GCC Warning Options — 所有警告选项
- GCC Optimization Options — 所有优化选项
- GCC Code Gen Options — 代码生成选项
- x86-64 psABI — x86-64 微架构级别规范
下一步
→ 05 - 预处理器详解:深入理解 GCC 预处理器的工作原理、宏定义技巧和条件编译。