GCC 编译过程
一、四个阶段
源文件 ──预处理──> 编译文件 ──编译──> 汇编文件 ──汇编──> 目标文件 ──链接──> 可执行文件
hello.c ──> hello.i ──> hello.s ──> hello.o ──> hello
| 阶段 | 输入 → 输出 | 命令 |
|---|---|---|
| 预处理 | .c → .i | gcc -E |
| 编译 | .i → .s | gcc -S |
| 汇编 | .s → .o | gcc -c |
| 链接 | .o → 可执行 | gcc(默认) |
gcc hello.c -o hello一步到位。
加上-v看具体命令。
二、常用编译选项
1. 优化
-O0 无优化(调试)
-O1 基本
-O2 常用
-O3 激进(可能引入 bug)
-Os 优化大小(嵌入式首选)
-Og 优化但保留调试信息(GCC 4.8+)
2. 调试
-g 生成调试信息
-ggdb 为 GDB 优化
-g3 宏信息
-fno-omit-frame-pointer 保留帧指针以便调用栈回溯
3. 警告
-Wall 常用警告
-Wextra 更多警告
-Werror 警告当错误(CI 必备)
-Wshadow 变量遮蔽
-Wpointer-arith 指针算术
-Wstrict-prototypes
4. 语言标准
-std=c99 / -std=c11 / -std=c17 / -std=c2x
-ansi / -pedantic 严格 ISO
5. 预处理
-DMACRO=val 定义宏
-UMACRO 取消宏
-include file 强制包含
-Idir 添加头文件路径
-M -MM -MMD 依赖生成
6. 输出
-c 只编译不链接
-o file 输出文件名
-S 只到汇编
-E 只到预处理
-v 详细输出
--save-temps 保存所有中间文件
-fverbose-asm 汇编加注释
7. 架构相关(嵌入式)
-march=armv7-a 指令集
-mtune=cortex-a9 调优
-mfpu=neon 启用 NEON
-mfloat-abi=hard 硬浮点 ABI
-mthumb Thumb 指令
-mlittle-endian 小端
8. 安全
-fstack-protector / -fstack-protector-strong 栈保护
-fcf-protection 控制流保护(CFI)
-D_FORTIFY_SOURCE=2 运行时检查
-pie -fPIE 位置无关可执行
-fPIC 位置无关代码
三、ELF 文件结构
可执行可链接格式 (ELF)
┌────────────────────┐
│ ELF Header │ 魔数、架构、入口点
├────────────────────┤
│ Program Headers │ 段(segment):运行时视图
├────────────────────┤
│ │
│ .text │ 代码
│ .rodata │ 只读数据
│ .data │ 已初始化数据
│ .bss │ 未初始化(不占文件空间)
│ .symtab │ 符号表
│ .strtab │ 字符串表
│ .debug_* │ 调试信息
│ .dynamic │ 动态链接信息
│ │
├────────────────────┤
│ Section Headers │ 节(section):链接视图
└────────────────────┘
查看工具:
file hello # 文件类型
readelf -h hello # ELF 头
readelf -l hello # 段
readelf -S hello # 节
readelf -s hello # 符号
readelf -d hello # 动态段
nm hello # 符号列表
objdump -d hello # 反汇编
objdump -h hello # 节摘要
objdump -t hello # 符号
size hello # 各段大小
strip hello # 删除符号
四、链接脚本(Linker Script)
嵌入式必须掌握。.lds 文件控制链接布局:
ENTRY(_start)
SECTIONS {
. = 0x40000000; /* 加载地址 */
.text : { *(.text*) }
.rodata : { *(.rodata*) }
. = ALIGN(4);
_data_load = LOADADDR(.data);
.data : AT(_data_load) {
*(.data*)
}
.bss : {
__bss_start = .;
*(.bss*) *(COMMON)
__bss_end = .;
}
. = ALIGN(16);
.stack : {
. = . + 0x10000;
_stack_top = .;
}
/DISCARD/ : { *(.comment) }
}
五、动态库 vs 静态库
静态库:.a → 链接时拷贝到可执行
动态库:.so → 运行时按需加载(位置无关)
嵌入式常用:musl libc + 静态链接,方便部署。
带复杂应用时:glibc + 动态链接。
六、链接器优化(黄金选项)
-Wl,--gc-sections 删除未引用节
-Wl,--as-needed 精简动态库依赖
-Wl,-z,relro -Wl,-z,now RELRO
-Wl,-z,noexecstack 非执行栈
七、踩坑清单
| 现象 | 原因 |
|---|---|
undefined reference to xxx |
没链接对应库 / 没声明 extern |
| multiple definition | 头文件没加 extern / include .c 文件 |
| stack smashing detected | 栈溢出 / -fstack-protector 触发 |
| segfault | 空指针 / 越界 / 写只读段 |
内核 BUG: scheduling while atomic |
持锁调用睡眠函数 |