GCC 完全指南 / 12 - 汇编输出与内联汇编
12 - 汇编输出与内联汇编
学习如何查看 GCC 生成的汇编代码,在 C/C++ 中使用内联汇编,理解 AT&T 与 Intel 语法差异。
12.1 生成汇编输出
基本用法
# 生成汇编文件
gcc -S main.c -o main.s
# 使用 Intel 语法(x86)
gcc -S -masm=intel main.c -o main.s
# 查看汇编与源码对应(-fverbose-asm)
gcc -S -fverbose-asm main.c -o main.s
# 带优化的汇编
gcc -S -O2 main.c -o main_opt.s
# 对比不同优化级别的汇编输出
diff <(gcc -S -O0 main.c -o -) <(gcc -S -O2 main.c -o -)
查看编译器优化效果
# 查看自动向量化是否生效
gcc -S -O2 -ftree-vectorizer-verbose=2 main.c -o main.s 2>&1
# 查看内联决策
gcc -S -O2 -fdump-ipa-inline main.c
# 生成 .inline 文件
# 查看所有优化 pass 的效果
gcc -S -O2 -fdump-tree-all main.c
# 生成大量 .tree.* 文件
12.2 AT&T 与 Intel 语法对比
语法差异
| 特性 | AT&T 语法(GCC 默认) | Intel 语法(NASM/YASM) |
|---|---|---|
| 操作数顺序 | src, dest | dest, src |
| 寄存器前缀 | %rax | rax |
| 立即数前缀 | $42 | 42 |
| 内存引用 | (%rax) 或 8(%rax) | [rax] 或 [rax+8] |
| 操作数大小后缀 | movl, movq | dword ptr, qword ptr |
| 段偏移 | %gs:0x10 | gs:[0x10] |
AT&T 语法示例
# AT&T 语法(GCC 默认输出)
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $42, -4(%rbp) # 局部变量
movl -4(%rbp), %eax
addl $8, %eax
leave
ret
Intel 语法示例
; Intel 语法(-masm=intel)
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 42 ; 局部变量
mov eax, DWORD PTR [rbp-4]
add eax, 8
leave
ret
GCC 生成 Intel 语法
# 生成 Intel 语法的汇编
gcc -S -masm=intel main.c -o main.s
# 使用 Intel 语法反汇编
objdump -d -M intel main.o
12.3 常见汇编模式识别
函数序言和尾声
# 标准函数序言 (Function Prologue)
func:
pushq %rbp # 保存旧的帧指针
movq %rsp, %rbp # 建立新的帧指针
subq $32, %rsp # 为局部变量分配栈空间
# 标准函数尾声 (Function Epilogue)
leave # 等价于 movq %rbp, %rsp; popq %rbp
ret # 返回
参数传递(x86-64 System V ABI)
# 前 6 个整数参数通过寄存器传递
# 第 1 个参数: %rdi
# 第 2 个参数: %rsi
# 第 3 个参数: %rdx
# 第 4 个参数: %rcx
# 第 5 个参数: %r8
# 第 6 个参数: %r9
# 更多参数: 栈(从右到左入栈)
# 浮点参数: %xmm0 - %xmm7
# 返回值: %rax(整数)/ %xmm0(浮点)
if-else 结构
// C 源码
int max(int a, int b) {
if (a > b) return a;
else return b;
}
# 对应汇编
max:
cmp %esi, %edi # 比较 a 和 b
mov %esi, %eax # 预设返回 b
cmovge %edi, %eax # 如果 a >= b,返回 a
ret
循环结构
// C 源码
int sum(int n) {
int s = 0;
for (int i = 0; i < n; i++) s += i;
return s;
}
# -O2 优化后的汇编
sum:
test %edi, %edi
jle .L3
leal -1(%rdi), %eax
imull %edi, %eax
shrl $31, %eax
addl %edi, %eax
sarl %eax
ret
.L3:
xorl %eax, %eax
ret
# 编译器将循环优化为 n*(n-1)/2 的公式!
12.4 内联汇编基础
基本语法
// 基本内联汇编
asm("指令");
// 扩展内联汇编(带约束)
asm("模板"
: 输出操作数 // 可选
: 输入操作数 // 可选
: 破坏列表); // 可选
简单示例
#include <stdio.h>
// 获取当前 CPU 周期计数
static inline uint64_t rdtsc(void) {
uint32_t lo, hi;
asm volatile("rdtsc"
: "=a"(lo), "=d"(hi) // 输出: lo → eax, hi → edx
: // 无输入
: // 无额外破坏
);
return ((uint64_t)hi << 32) | lo;
}
int main(void) {
uint64_t start = rdtsc();
// ... 某些操作 ...
uint64_t end = rdtsc();
printf("Cycles: %lu\n", end - start);
return 0;
}
常用约束
| 约束 | x86-64 含义 |
|---|---|
"r" | 任意通用寄存器 |
"a" | %rax/%eax/%ax/%al |
"b" | %rbx/%ebx |
"c" | %rcx/%ecx |
"d" | %rdx/%edx |
"S" | %rsi/%esi |
"D" | %rdi/%edi |
"i" | 立即数 |
"m" | 内存操作数 |
"g" | 寄存器、内存或立即数 |
"0" | 与第 0 个操作数相同的位置 |
输出和输入约束前缀
| 前缀 | 说明 |
|---|---|
"=" | 只写(覆盖旧值) |
"+ | 读写 |
"&" | 早期破坏(输出操作数不与输入重叠) |
示例:原子操作
// 原子比较并交换
static inline int atomic_cas(int *ptr, int old_val, int new_val) {
int result;
asm volatile(
"lock cmpxchgl %2, %1"
: "=a"(result), "+m"(*ptr)
: "r"(new_val), "0"(old_val)
: "memory"
);
return result;
}
// 原子递增
static inline int atomic_inc(int *ptr) {
int result;
asm volatile(
"lock xaddl %0, %1"
: "=r"(result), "+m"(*ptr)
: "0"(1)
: "memory"
);
return result;
}
内存屏障
// 编译器屏障(阻止编译器重排)
#define barrier() asm volatile("" ::: "memory")
// 完整内存屏障
#define mb() asm volatile("mfence" ::: "memory")
#define rmb() asm volatile("lfence" ::: "memory")
#define wmb() asm volatile("sfence" ::: "memory")
12.5 内联汇编高级技巧
多指令模板
// 交换两个值(使用 XOR 交换)
void swap_xor(int *a, int *b) {
asm volatile(
"movl (%0), %%eax\n\t"
"xorl (%1), %%eax\n\t"
"movl %%eax, (%0)\n\t"
"xorl (%1), %%eax\n\t"
"movl %%eax, (%1)\n\t"
"xorl (%0), %%eax\n\t"
"movl %%eax, (%0)"
:
: "r"(a), "r"(b)
: "eax", "memory"
);
}
条件码约束
// 使用 GCC 内联汇编中的条件码
int check_sign(int x) {
int result;
asm(
"testl %1, %1\n\t"
"setg %b0" // 设置 result 如果 x > 0
: "=q"(result)
: "r"(x)
: "cc" // 告诉编译器条件码被修改
);
return result;
}
12.6 GCC Built-in 函数(内联汇编的替代)
很多场景不需要直接写内联汇编,GCC 提供了内置函数。
#include <stdint.h>
// 位操作
int leading_zeros = __builtin_clz(x); // 前导零计数
int trailing_zeros = __builtin_ctz(x); // 尾随零计数
int popcount = __builtin_popcount(x); // 置位计数
// 字节序转换
uint32_t swapped = __builtin_bswap32(x);
// 溢出检查
int overflow = __builtin_add_overflow(a, b, &result);
// 不可达标记
__builtin_unreachable();
// 内存操作
__builtin_memcpy(dst, src, n);
__builtin_memset(dst, val, n);
// CPU 特性检测
if (__builtin_cpu_supports("avx2")) { ... }
12.7 实用汇编分析技巧
对比不同编译器的输出
# GCC 输出
gcc -S -O2 -masm=intel main.c -o gcc_output.s
# Clang 输出
clang -S -O2 -masm=intel main.c -o clang_output.s
# 在线工具: https://godbolt.org/
查看特定函数的汇编
# 反汇编特定函数
objdump -d --disassemble=my_function main.o
# 反汇编并显示源码
objdump -d -S main.o
# 只反汇编 .text 段
objdump -d -j .text main.o
# 使用 Intel 语法反汇编
objdump -d -M intel main.o
Compiler Explorer
Godbolt Compiler Explorer (https://godbolt.org):
- 在线查看不同编译器/优化级别的汇编输出
- 源码与汇编高亮对应
- 支持 GCC、Clang、MSVC 等
- 支持多种架构 (x86、ARM、RISC-V)
- 支持不同的汇编语法风格
要点回顾
| 要点 | 核心内容 |
|---|---|
-S 选项 | 生成汇编代码,-fverbose-asm 添加注释 |
| AT&T vs Intel | 操作数顺序相反,AT&T 用 % 和 $ 前缀 |
| 内联汇编 | asm volatile("模板" : 输出 : 输入 : 破坏) |
| 约束 | "r" 寄存器、"m" 内存、"a" %rax |
| Built-in | __builtin_clz、__builtin_popcount 等替代内联汇编 |
| Compiler Explorer | godbolt.org 在线查看汇编 |
注意事项
volatile很重要: 内联汇编不加volatile可能被编译器优化掉。
列出所有破坏的寄存器: 如果汇编修改了寄存器但未在破坏列表中声明,会导致难以调试的 bug。
"memory"破坏: 如果汇编修改了内存,必须在破坏列表中加上"memory"。
优先使用 Built-in:
__builtin_*函数让编译器有更好的优化机会,可移植性也更好。
扩展阅读
- GCC Extended Asm — 内联汇编文档
- GCC Constraints — 约束参考
- System V ABI — x86-64 调用约定
- Compiler Explorer — 在线汇编查看器
- Agner Fog 汇编优化 — 指令延迟和吞吐量
下一步
→ 13 - GCC 扩展特性:了解 GCC 独有的扩展属性、内置函数和向量化支持。