title: gcc编译选项 created: 2026-08-31 type: source tags:
GCC(GNU Compiler Collection)是 Linux 下最核心的编译工具链,支持 C、C++、Fortran 等多种语言。
gcc main.c -o main # 一步完成预处理→编译→汇编→链接
./main # 运行生成的可执行文件
源代码(.c) → 预处理(.i) → 编译(.s) → 汇编(.o) → 链接(ELF可执行文件)
cpp gcc -E gcc -S as ld
| 阶段 | 工具 | gcc选项 | 输入 | 输出 | 作用 |
|---|---|---|---|---|---|
| 预处理 | cpp | -E |
.c |
.i |
展开宏、处理#include、删除注释 |
| 编译 | gcc/cc1 | -S |
.i |
.s |
将预处理代码翻译为汇编 |
| 汇编 | as | -c |
.s |
.o |
将汇编翻译为机器码(目标文件) |
| 链接 | ld | (默认) | .o |
无后缀 | 合并目标文件和库,生成可执行文件 |
# 分步演示
gcc -E main.c -o main.i # 预处理
gcc -S main.i -o main.s # 编译为汇编
gcc -c main.s -o main.o # 汇编为目标文件
gcc main.o -o main # 链接
| 选项 | 作用 | 示例 |
|---|---|---|
-o |
指定输出文件名 | gcc main.c -o app |
-c |
只编译不链接,生成 .o 文件 |
gcc -c main.c |
-E |
只预处理,输出到 stdout | gcc -E main.c > main.i |
-S |
只编译为汇编,生成 .s 文件 |
gcc -S main.c |
-pipe |
使用管道代替临时文件,加速编译 | gcc -pipe main.c -o main |
| 选项 | 作用 |
|---|---|
-w |
关闭所有警告 |
-Wall |
开启大部分常见警告(推荐始终开启) |
-Wextra |
开启额外警告 |
-Werror |
将所有警告视为错误,阻止编译 |
-Wno-xxx |
关闭特定警告,如 -Wno-unused-variable |
# 推荐的生产编译组合
gcc -Wall -Wextra -Werror main.c -o main
| 选项 | 作用 |
|---|---|
-g |
生成调试信息(供 gdb 使用) |
-g3 |
生成更详细的调试信息(含宏) |
gcc -g main.c -o main_debug
gdb ./main_debug
| 选项 | 作用 | 编译速度 | 运行速度 | 调试友好 |
|---|---|---|---|---|
-O0 |
不优化(默认) | 最快 | 最慢 | 最佳 |
-O1 |
基础优化 | 较快 | 较快 | 较好 |
-O2 |
更多优化 | 较慢 | 更快 | 一般 |
-O3 |
激进优化 | 最慢 | 最快 | 较差 |
-Os |
优化体积 | 较慢 | 较快 | 较好 |
-Ofast |
极致优化(不符合标准) | 最慢 | 最快 | 差 |
注意: 调试阶段使用
-O0,发布时使用-O2或-O3。-O3可能改变程序行为(如循环展开、向量化)。
| 选项 | 作用 | 示例 |
|---|---|---|
-I dir |
添加头文件搜索路径 | gcc -I./include main.c |
-L dir |
添加库文件搜索路径 | gcc -L./lib main.c -lmylib |
-l lib |
链接指定库(顺序重要) | gcc main.c -lm -lpthread |
# 搜索顺序:-I 指定路径 → 当前目录 → /usr/include → /usr/local/include
# 库搜索顺序:-L 指定路径 → /lib → /usr/lib → /usr/local/lib
| 选项 | 作用 | 示例 |
|---|---|---|
-D NAME |
定义宏,值为 1 | gcc -DDEBUG main.c |
-D NAME=V |
定义宏为指定值 | gcc -DMAX_BUF=1024 main.c |
-U NAME |
取消宏定义 | gcc -UMAX_BUF main.c |
# 条件编译实战
gcc -DDEBUG -DVERSION=\"1.0\" main.c -o main
// main.c
#ifdef DEBUG
printf("Debug mode\n");
#endif
printf("Version: %s\n", VERSION);
| 选项 | 作用 |
|---|---|
-std=c99 |
使用 C99 标准 |
-std=c11 |
使用 C11 标准(推荐) |
-std=gnu99 |
C99 + GNU 扩展 |
-std=gnu11 |
C11 + GNU 扩展 |
-std=c++11 |
使用 C++11 标准 |
gcc -std=c11 main.c -o main
注意: GNU 扩展允许变长数组、
typeof等非标准特性。可移植代码建议用-std=c11。
| 选项 | 作用 |
|---|---|
-v |
显示编译过程详细信息 |
-save-temps |
保留所有中间文件(.i/.s/.o) |
-static |
静态链接 |
-shared |
生成共享库(.so) |
-fPIC |
生成位置无关代码(共享库必需) |
-fsyntax-only |
只检查语法,不生成目标文件 |
-print-search-dirs |
显示 GCC 搜索路径 |
处理所有 # 开头的指令:
gcc -E main.c -o main.i
// main.c
#include <stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
// 预处理后,MAX 宏被展开,#include 被替换为 stdio.h 的内容
// 注释被删除
将预处理后的代码翻译为汇编语言,进行:
-O 级别)生成目标架构的汇编代码
gcc -S main.i -o main.s
将汇编代码翻译为机器码,生成目标文件(.o):
as main.s -o main.o
# 等价于
gcc -c main.s -o main.o
将多个 .o 文件和库合并为可执行文件:
ld main.o -o main -lc /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o
# 实际上 gcc 会自动调用正确的 ld 配置
在宿主机(x86 PC)上编译出目标机(如 ARM 嵌入式设备)可运行的程序。
宿主机(Host): x86_64-linux 运行 gcc
目标机(Target): arm-linux-gnueabihf 运行编译产物
# 工具链前缀命名规则:{arch}-{vendor}-{os}-{abi}-
arm-linux-gnueabihf-gcc # ARM 32位,硬浮点
aarch64-linux-gnu-gcc # ARM 64位(AArch64)
arm-none-eabi-gcc # 裸机 ARM(无 OS)
# 在 x86 PC 上编译 ARM 程序
arm-linux-gnueabihf-gcc -Wall -O2 main.c -o main_arm
# 查看生成的二进制
file main_arm
# 输出:main_arm: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV)...
# 目录结构
project/
├── Makefile
├── src/
│ ├── main.c
│ └── led.c
└── include/
└── led.h
# Makefile — 嵌入式交叉编译
CC = arm-linux-gnueabihf-gcc
CFLAGS = -Wall -O2 -I./include -DUSE_GPIO
LDFLAGS = -static
TARGET = led_app
SRCS = src/main.c src/led.c
OBJS = $(SRCS:.c=.o)
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
src/%.o: src/%.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: all clean
# 内核模块编译
arm-linux-gnueabihf-gcc -Wall -O2 -D__KERNEL__ -DMODULE \
-I/path/to/kernel/include -c hello.c -o hello.o
# 或使用 Kbuild 系统
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
fatal error: xxx.h: No such file or directory |
头文件未找到 | 加 -I/path/to/include |
undefined reference to 'xxx' |
链接时找不到函数定义 | 检查库链接顺序,加 -l |
multiple definition of 'xxx' |
重复定义 | 检查头文件中是否有函数定义 |
implicit declaration of function 'xxx' |
未声明就使用 | 添加函数声明或检查头文件 |
expected ')' before 'xxx' |
语法错误或宏未定义 | 检查宏定义和括号匹配 |
# 1. 查看预处理结果,确认宏展开
gcc -E main.c | head -50
# 2. 查看汇编,确认优化是否符合预期
gcc -S -O2 main.c
cat main.s
# 3. 查看符号表
nm main.o
objdump -t main.o
# 4. 查看链接搜索路径
gcc -print-search-dirs
# 5. 保留中间文件,逐阶段排查
gcc -save-temps -Wall main.c -o main
-g 配合 -O0,发布时去掉 -g 或用 -O2 -g(可生成带调试信息的优化版本)gcc main.c -lm -lpthread,而非 -lpthread -lm-static;桌面应用默认动态链接-Werror 在 CI/CD 中很有用,但开发阶段可关闭以加快迭代A: 预处理(展开宏、处理头文件)→ 编译(翻译为汇编)→ 汇编(翻译为机器码)→ 链接(合并目标文件和库,解析符号引用)。
-Wall -Wextra 和 -Werror 的区别?A: -Wall -Wextra 开启更多警告;-Werror 将警告升级为错误,阻止编译。组合使用可在开发阶段尽早发现问题。
A: 在一种架构的机器上编译出另一种架构的程序。嵌入式开发中,PC(x86)上编译出 ARM 设备上运行的程序,因为目标设备资源有限,不适合直接编译。
-O0 和 -O3 的区别?调试时该用哪个?A: -O0 不优化,代码与源码一一对应,适合调试;-O3 激进优化(循环展开、内联、向量化),代码可能与源码差异大,调试困难。调试用 -O0 -g,发布用 -O2 或 -O3。
A: 确认:1)函数实现是否编译进了目标文件;2)库是否正确链接(-l 选项);3)链接顺序是否正确(被依赖的库放后面);4)是否混淆了 32/64 位。
-std=c11 和 -std=gnu11 的区别?A: -std=gnu11 在 C11 基础上启用 GNU 扩展(如语句表达式 ({...})、typeof、零长度数组等)。可移植代码用 -std=c11,Linux 内核代码用 GNU 扩展。