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 | "=/+" + 约束修饰符 + 变量 |
输入部
用来描述在指令部只能被读取访问的 C 语言变量以及约束条件
- 输入部描述的参数是只有只读属性,不要试图修改输入部的参数内容,因为 GCC 编译器假定,输入部的参数的内容在内嵌汇编之前和之后都是一致的
- 在输入部中不能使用”=”或者”+“约束条件,否则编译器会报错
- 输入部可以是空的
损坏部(Clobbers)
- “memory”告诉 GCC 编译器内联汇编指令改变了内存中的值,强迫编译器在执行该汇编代码前存储所有缓存中的值,在执行完汇编代码之后重新加载该值,目的是防止编译乱序
- ”cc”表示内嵌汇编代码修改了状态寄存器相关的标志位
指令部的参数表示
- %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 代码中的某个标签,实现条件分支。