GCC内嵌汇编
时间轴
2025-09-27
init
参考文档:
内嵌汇编(Inline Assembly Language)在 C 语言中嵌入汇编代码
目的:
- 优化:针对特定重要代码(time-sensitive 进行优化)
- C 语言需要访问某些特殊指令来实现特殊功能,比如内存屏障指令
内存汇编两种模式
- 基础内嵌汇编
- 扩展内嵌汇编
基础内嵌汇编
格式:
1 | asm asm-qualifiers(AssemblerInstructions) |
- asm 关键字:表明这是一个 GNU 扩展
修饰词(qualifiers)
- volatile:在基础内嵌汇编中通常不需要这个修饰词
- inline:内联,asm 汇编的代码会尽可能小
汇编代码块(AssemblerInstructions)
GCC 编译器把内嵌汇编当成一个字符串
GCC 编译不会去解析和分析内嵌汇编
多条汇编指令,需要使用”\n\t“来换行
GCC 的优化器,可以移动汇编指令的前后位置。如果你需要保存汇编指令的顺序,最后使用多个内嵌汇编的方式

扩展内嵌汇编

- 格式
- asm 关键字:表明这个是一个 GNU 扩展
- 修饰词(asm-qualifiers)
- volatile:用来关闭 GCC 优化
- inline 内联,asm 汇编的代码会尽可能小
- goto 在内嵌会便利会跳转到 C 语言的标签里

输出部:
用于描述在指令部中可以被修改的 C 语言变量以及约束条件
- 每个输出约束通常以”=”开头,接着的字母表示对操作数类型的说明,然后是关于变量结合的约束
- 输出部通常使用”=“或者”+”作为输出约束,其中”=“表示被修饰的操作数只具有可写属性,”+“表示被修饰的操作数只具有可读可写属性
- 输出部可以是空的
1 | "=/+" + 约束修饰符 + 变量 |
约束修饰符常用
- ‘r’ A register operand is allowed provided that it is in a general register.
输入部
用来描述在指令部只能被读取访问的 C 语言变量以及约束条件
- 输入部描述的参数是只有只读属性,不要试图修改输入部的参数内容,因为 GCC 编译器假定,输入部的参数的内容在内嵌汇编之前和之后都是一致的
- 在输入部中不能使用”=”或者”+“约束条件,否则编译器会报错
- 输入部可以是空的
损坏部(Clobbers)
- 汇编代码可能修改除了输出之外的寄存器。
- 如果不告诉编译器,会导致编译器认为这些寄存器保持不变,最终可能出现错误或不可预测的行为。
- Clobber 也是一种内存屏障(尤其
"memory"),确保编译器不会重排或缓存变量。
| 名称 | 作用 |
| —————————- | —————————————————————————— |
|"x0","x1", … | 通用寄存器被修改 |
|"cc"| 条件码寄存器(flags)被修改 |
|"memory"| 汇编访问了内存,保证编译器刷新寄存器到内存并重新加载 |
|"redzone"| 在 x86-64 的 redzone 区域使用栈空间 |- “memory”告诉 GCC 编译器内联汇编指令改变了内存中的值,强迫编译器在执行该汇编代码前存储所有缓存中的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序
- ”cc”表示内嵌汇编代码修改了状态寄存器相关的标志位
不允许在 clobber 列表里写 栈指针寄存器(esp/rsp)。
不要重复列出输出寄存器。
四种可以情况可以组合,用逗号分割即可
- 指令部的参数表示
- %0 对应输出输入部的第一个参数,%1 表示第二个参数


输出和输入部的约束修饰符

输出部和输入部的约束修饰符——通用

输出部和输入部的约束修饰符——ARM64

汇编符号名字来代替前缀%
%[name]→ 引用约束变量%w[name]→ 引用 低 32 位寄存器(如w0, w1)%x[name]→ 引用 完整 64 位寄存器(如x0, x1)

实验 1:实现简单的 memcpy 函数


陷阱与坑
- GDB 不能单步调试内嵌汇编
- 输出部和输入部的修饰符不能用错,否则程序会跑错
实验 3:使用内嵌汇编实现 memset 函数

内嵌汇编的高级玩法:和宏结合
- 技巧 1:使用了 C 语言的#运算符。在带参数的宏中,“#”运算符作为一个预处理运算符,可以把记号转换为字符串

1 |
可以用这个宏来生成多个原子操作函数:
1 | ATOMIC_OP(add, "addl") |
这会展开为:
1 | static inline void atomic_add(int i, atomic_t *v) { |
图片中的这个宏使用 #asm_op 是因为 它需要将宏参数 asm_op 转换成字符串字面量,用于字符串拼接或输出。这种语法叫做 宏字符串化(stringification)
asm_op 是宏参数,在宏展开时会替换为你提供的值(比如 "addl")。
它已经是一个字符串,比如你调用:
1 | ATOMIC_OP(add, "addl") |
宏展开后是:
1 | __asm__ __volatile__( |
而这样:
1 | ATOMIC_OP(add, addl) |
宏展开后是
1 | __asm__ __volatile__( |
则不是
##name —— 符号连接(token pasting)运算符
把宏参数和前后的标识符直接拼接成一个新的标识符(不是字符串)。
常用来生成变量名、函数名等。
1 |
|
实验 4:使用内嵌汇编与宏的结合


实验 5:实现读写系统寄存器的宏


这里用到了GNU C 的语句表达式 (statement expression) 语法:
({ ... }) 是什么
这是 GNU 扩展,不是标准 C。
它让一段代码块既能像语句一样执行,也能返回一个值。
语法规则:
({ statement1; statement2; ...; expression; })
代码块里最后的那个 表达式(不加分号)就是返回值。

内嵌汇编:goto
内嵌汇编的 goto 模板,可以跳转到 C 语言的 label 标签里

- Goto 模板的输出部必须为空
- 新增一个 gotolabels 的部,里面列出了 C 语言的 label,是允许跳转的 label

实验 6:goto 模板的内嵌汇编


%l[label] 是 GCC 内联汇编中 asm goto 语法的特殊写法,表示跳转到 C 代码中的标签 label。
详细解释:
%l[...]是告诉编译器这是一个标签符号(label),而不是普通的寄存器或立即数。label是你在 C 代码里定义的标签名,比如你代码中的label:。asm goto允许汇编代码通过条件跳转直接跳转到 C 代码中的某个标签,实现条件分支。







