GCC(GNU Compiler Collection)是一套广泛使用的开源编译工具链,支持多种编程语言(如 C、C++、Objective-C、Fortran 等),主要用于 Linux 和嵌入式开发环境。
组成
GCC 工具链主要由以下几个核心工具组成:
工具 | 作用 |
---|---|
gcc/g++ | GNU 编译器(C 语言使用 gcc ,C++ 语言使用 g++ ) |
as | GNU 汇编器,将汇编代码转换为目标代码 |
ld | GNU 链接器,负责链接目标文件和库,生成可执行文件 |
ar | 归档工具,用于创建、修改静态库(.a 文件) |
nm | 查看目标文件或库文件的符号表 |
objdump | 反汇编工具,查看二进制文件的详细信息 |
readelf | 查看 ELF 文件(Linux 可执行文件格式)的头信息 |
strip | 去除二进制文件中的符号信息,减小体积 |
make | 构建管理工具,执行自动化编译 |
gdb | GNU 调试器,调试 C/C++ 程序 |
编译流程
GCC 编译一个程序的完整流程通常分为 4 个阶段:
预处理(Preprocessing)
- 处理
#include
、#define
、#ifdef
等预处理指令 - 生成扩展的源代码文件(
.i
或.ii
)
命令示例:
gcc -E main.c -o main.i
编译(Compilation)
- 预处理后的
.i
文件转换成汇编代码(.s
文件)
命令示例:
gcc -S main.i -o main.s
汇编(Assembly)
- 汇编代码
.s
转换为目标文件.o
命令示例:
gcc -c main.s -o main.o
链接(Linking)
- 将多个
.o
文件以及库文件链接成最终的可执行文件
命令示例:
gcc main.o -o main
完整编译流程:
gcc main.c -o main
编译选项
基础选项
选项 | 作用 |
---|---|
-o <文件> | 指定输出文件名 |
-c | 只编译但不链接(生成 .o 文件) |
-S | 生成汇编代码 |
-E | 仅进行预处理 |
-v | 显示详细编译过程 |
优化选项
选项 | 作用 |
---|---|
-O0 | 关闭优化(默认) |
-O1 | 轻量优化 |
-O2 | 启用更多优化 |
-O3 | 极致优化(可能增加代码体积) |
-Os | 优化代码体积 |
-Ofast | 最高速优化(可能不符合标准) |
调试选项
选项 | 作用 |
---|---|
-g | 生成调试信息(用于 GDB) |
-ggdb | 生成 GDB 兼容的调试信息 |
-fno-omit-frame-pointer | 保留帧指针,便于调试 |
警告与错误
选项 | 作用 |
---|---|
-Wall | 开启大部分警告 |
-Wextra | 开启额外警告 |
-Werror | 将所有警告视为错误 |
-pedantic | 强制符合标准 |
架构与平台
选项 | 作用 |
---|---|
-m32 / -m64 | 生成 32 位或 64 位代码 |
-march=<cpu> | 生成适用于特定 CPU 架构的代码 |
-mfpu=<type> | 指定浮点单元(适用于 ARM) |
静态库与动态库
静态库(.a)
-
编译:
gcc -c libadd.c -o libadd.o ar rcs libadd.a libadd.o
-
使用:
gcc main.c -L. -ladd -o main_static
动态库(.so)
-
编译:
gcc -shared -fPIC libadd.c -o libadd.so
-
使用:
gcc main.c -L. -ladd -o main_shared export LD_LIBRARY_PATH=.
GCC 与 Makefile
如果项目包含多个源文件,手动编译比较麻烦,推荐使用 Makefile
自动化编译。
示例 Makefile
:
CC = gcc
CFLAGS = -Wall -O2all: mainmain: main.o add.o$(CC) $^ -o $@%.o: %.c$(CC) $(CFLAGS) -c $< -o $@clean:rm -f *.o main
执行:
make # 编译
make clean # 清理
GCC 与 GDB 调试
使用 -g
选项编译:
gcc -g main.c -o main
gdb ./main
常用 GDB 命令:
break main # 设置断点
run # 运行程序
next # 单步执行(不进入函数)
step # 单步执行(进入函数)
print var # 查看变量值
bt # 查看调用栈
优化策略
优化级别(-O 选项)
GCC 提供不同级别的优化,可通过 -O
(字母 O 不是数字 0)指定:
选项 | 作用 |
---|---|
-O0 | 无优化(默认),编译速度快,便于调试 |
-O1 | 基本优化,优化代码但不显著增加编译时间 |
-O2 | 较强优化,启用所有不会影响程序正确性的优化 |
-O3 | 最高级别优化,比 -O2 增加更多优化,如循环展开和向量化 |
-Os | 优化代码大小,适用于嵌入式系统 |
-Ofast | 极速优化,在 -O3 的基础上忽略严格标准,如浮点计算优化 |
-Og | 适用于调试的优化,比 -O0 稍微优化,但保持调试友好 |
常见优化级别建议
- 开发阶段:
-Og
(优化但保留调试信息) - 一般程序:
-O2
(平衡优化和编译时间) - 极致优化:
-O3
或-Ofast
(但可能会增加代码体积) - 嵌入式系统:
-Os
(减少代码大小)
代码生成优化
架构优化
可以针对特定 CPU 进行优化,使编译器生成更高效的指令。
选项 | 作用 |
---|---|
-march=<cpu> | 生成针对特定 CPU 体系结构的代码(如 -march=native 自动检测 CPU) |
-mtune=<cpu> | 仅优化指令调度,仍然兼容其他 CPU |
-m32 / -m64 | 生成 32 位或 64 位代码 |
-mfpu=<type> | 选择浮点单元(适用于 ARM) |
示例(自动检测 CPU):
gcc -O2 -march=native -o program program.c
循环优化
循环优化可以减少不必要的计算,提高效率。
选项 | 作用 |
---|---|
-funroll-loops | 进行循环展开,减少循环跳转开销 |
-floop-optimize | 启用基本循环优化 |
-ftree-vectorize | 启用自动向量化,利用 SIMD 指令 |
-fno-tree-vectorize | 禁用自动向量化(默认启用 -O3 时开启) |
示例(循环展开 + 向量化):
gcc -O3 -funroll-loops -ftree-vectorize -o program program.c
函数优化
选项 | 作用 |
---|---|
-finline-functions | 允许编译器自动内联小函数 |
-fno-inline-functions | 禁用函数内联 |
-fno-strict-aliasing | 允许不同类型的指针安全访问(防止错误优化) |
示例(强制内联优化):
gcc -O3 -finline-functions -o program program.c
分支预测优化
选项 | 作用 |
---|---|
-fprofile-generate / -fprofile-use | 使用运行时数据 进行优化(适用于热点代码) |
-fbranch-probabilities | 优化分支预测(减少 CPU 分支错误) |
示例(使用运行数据优化):
gcc -O2 -fprofile-generate -o program program.c
./program # 运行一次,收集数据
gcc -O2 -fprofile-use -o program program.c
链接优化
静态 vs 动态链接
选项 | 作用 |
---|---|
-static | 静态链接,使可执行文件不依赖动态库 |
-shared | 生成动态库(.so 文件) |
-fPIC | 生成位置无关代码,用于共享库 |
示例(生成动态库):
gcc -shared -fPIC lib.c -o lib.so
链接时优化(LTO)
LTO(Link-Time Optimization)允许在链接时进一步优化整个程序。
选项 | 作用 |
---|---|
-flto | 启用链接时优化 |
-flto=auto | 自动调整 LTO 线程数 |
示例(LTO 优化):
gcc -O2 -flto -o program program.c
其他优化
去除未使用代码
选项 | 作用 |
---|---|
-ffunction-sections | 让每个函数放入单独的段 |
-fdata-sections | 让全局变量放入单独的段 |
-Wl,--gc-sections | 移除未使用的代码(与 -ffunction-sections 配合使用) |
示例(精简可执行文件):
gcc -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -o program program.c
优化浮点计算
选项 | 作用 |
---|---|
-ffast-math | 允许不严格遵守 IEEE 754 标准,提高浮点运算性能 |
-funsafe-math-optimizations | 允许不安全的浮点优化 |
示例(快速浮点运算):
gcc -O3 -ffast-math -o program program.c
并行编译
选项 | 作用 |
---|---|
-pipe | 使用管道而非临时文件,提高编译速度 |
-fopenmp | 启用 OpenMP 并行计算 |
-pthread | 启用多线程支持 |
示例(使用 OpenMP 并行优化):
gcc -O2 -fopenmp -o program program.c
总结
目标 | 推荐优化选项 |
---|---|
开发调试 | -Og -g |
一般优化 | -O2 |
极限优化 | -O3 -march=native -funroll-loops -ftree-vectorize -flto |
小代码体积 | -Os -ffunction-sections -fdata-sections -Wl,--gc-sections |
浮点运算优化 | -Ofast -ffast-math |
多线程优化 | -O2 -fopenmp |
交叉编译
什么是交叉编译?
交叉编译(Cross Compilation) 指在一个平台(如 x86_64)上编译生成另一个平台(如 ARM、RISC-V、MIPS)可执行的代码。常见应用包括:
- 嵌入式开发(如树莓派、ESP32、STM32)
- 不同 CPU 架构移植(如 x86 生成 ARM 代码)
- 交叉工具链构建(如构建 Android、Linux 内核)
交叉编译工具链
交叉编译需要 交叉编译工具链(Cross Toolchain),主要包括:
- 交叉编译器(gcc、g++):如
arm-linux-gnueabi-gcc
- 汇编器(as):生成目标机器的汇编代码
- 链接器(ld):将目标文件链接成可执行文件
- 库文件(libc, libm, libstdc++):目标平台所需的标准库
- 调试工具(gdb):远程调试目标平台的程序
常见交叉编译工具链
目标架构 | 工具链前缀 | 适用平台 |
---|---|---|
ARM 32-bit | arm-linux-gnueabi-gcc | 旧版 ARM |
ARM 32-bit EABI | arm-linux-gnueabihf-gcc | 含硬件浮点的 ARM |
ARM 64-bit (AArch64) | aarch64-linux-gnu-gcc | 64 位 ARM |
MIPS 32-bit | mips-linux-gnu-gcc | 32 位 MIPS |
MIPS 64-bit | mips64-linux-gnuabi64-gcc | 64 位 MIPS |
RISC-V 32-bit | riscv32-unknown-linux-gnu-gcc | 32 位 RISC-V |
RISC-V 64-bit | riscv64-unknown-linux-gnu-gcc | 64 位 RISC-V |
安装方式(Ubuntu/Debian):
sudo apt update
sudo apt install gcc-aarch64-linux-gnu # 安装 AArch64 交叉编译器
交叉编译基本使用
假设要编译 hello.c
为 ARM 64 位可执行程序:
#include <stdio.h>
int main() {printf("Hello, ARM!\n");return 0;
}
普通编译(本机 x86_64)
gcc hello.c -o hello_x86
file hello_x86 # 查看编译结果
输出(x86 机器):
ELF 64-bit LSB executable, x86-64
交叉编译 ARM 64 位
aarch64-linux-gnu-gcc hello.c -o hello_arm64
file hello_arm64
输出(ARM 64 可执行文件):
ELF 64-bit LSB executable, ARM aarch64
此时 hello_arm64
不能在 x86_64 直接运行,需要拷贝到目标 ARM 设备运行。
交叉编译静态 & 动态链接
静态编译
aarch64-linux-gnu-gcc hello.c -static -o hello_static
优点:
- 生成独立可执行文件,无需依赖目标系统的库。
- 适合嵌入式系统。
缺点:
- 文件较大,占用更多存储空间。
动态编译
aarch64-linux-gnu-gcc hello.c -o hello_dynamic
ldd hello_dynamic # 检查动态库依赖
优点:
- 文件更小,依赖目标系统的动态库。
- 适合桌面 Linux 系统。
缺点:
- 可能出现动态库不兼容的问题,需要确保目标系统有相应的库。
交叉编译 Makefile
如果项目较复杂,使用 Makefile
自动化编译:
CC = aarch64-linux-gnu-gcc
CFLAGS = -O2 -Wall
TARGET = hello_armall: $(TARGET)$(TARGET): hello.c$(CC) $(CFLAGS) $< -o $@clean:rm -f $(TARGET)
执行:
make
交叉编译库文件
编译静态库
aarch64-linux-gnu-gcc -c add.c -o add.o
ar rcs libadd.a add.o
在程序中使用:
aarch64-linux-gnu-gcc main.c -L. -ladd -o main
编译动态库
aarch64-linux-gnu-gcc -shared -fPIC add.c -o libadd.so
运行时需要 LD_LIBRARY_PATH
:
export LD_LIBRARY_PATH=.
./main
远程调试(GDB)
如果目标设备没有 GDB,可使用 远程调试:
目标设备(ARM)
gdbserver :1234 ./hello_arm
开发机(x86)
aarch64-linux-gnu-gdb hello_arm
target remote <目标设备IP>:1234
QEMU 模拟运行
如果没有 ARM 设备,可用 QEMU 运行 ARM 可执行文件:
sudo apt install qemu-user
qemu-aarch64 ./hello_arm
交叉编译 Linux 内核
获取 Linux 内核
git clone --depth=1 https://github.com/torvalds/linux.git
cd linux
交叉编译
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
生成 Image
可用于 ARM64 设备。
交叉编译 RootFS
嵌入式开发通常需要交叉编译 RootFS,可使用 Buildroot:
git clone https://git.buildroot.net/buildroot
cd buildroot
make menuconfig
make
生成完整的嵌入式系统 RootFS。
总结
任务 | 命令 |
---|---|
安装 ARM 交叉编译器 | sudo yum install gcc-aarch64-linux-gnu |
编译 ARM 64 可执行文件 | aarch64-linux-gnu-gcc hello.c -o hello_arm64 |
编译静态库 | ar rcs libadd.a add.o |
编译动态库 | aarch64-linux-gnu-gcc -shared -fPIC add.c -o libadd.so |
QEMU 运行 ARM 程序 | qemu-aarch64 ./hello_arm |
远程 GDB 调试 | gdbserver :1234 ./hello_arm |