C 中的内联汇编语言
内容提要
本文介绍C语言内联汇编。基本asm语法为asm(汇编指令),隐式volatile,但GCC不解析指令。扩展asm可读写C变量,语法含汇编模板、输出约束、输入约束和clobber列表。约束“r”表示用通用寄存器,“=r”用于输出。若汇编有副作用,需用clobber列表显式列出被修改的寄存器,避免GCC误用。
延伸解读
基本 asm 的局限与风险
基本 asm 语法简单,但 GCC 不解析汇编指令,对其含义和有效性一无所知。这意味着编译器不会检查指令是否正确,也不会感知指令对寄存器或内存的影响。因此,基本 asm 仅适合独立、无副作用的操作,如读取系统寄存器。若汇编涉及修改寄存器或内存,必须使用扩展 asm 并显式声明,否则可能引发难以调试的错误。
扩展 asm 的约束机制
扩展 asm 通过输出约束、输入约束和 clobber 列表与 C 变量交互。约束“r”表示使用通用寄存器,用于输入;“=r”表示只写寄存器,用于输出。例如,asm volatile("msr pmintenset_el1, %0" : : "r" ((u64)(0 << 31))) 将 C 表达式放入通用寄存器。运行时,GCC 分配寄存器填充 %0,并绑定 C 变量,实现汇编与 C 的数据传递。
clobber 列表的必要性
当汇编代码有副作用,如修改其他寄存器时,GCC 无法自动感知,因为它不解析汇编指令。此时必须用 clobber 列表显式列出被修改的寄存器,例如 "eax", "ecx"。这样 GCC 就不会依赖这些寄存器来保存其他值,避免优化导致的数据冲突。忽略 clobber 列表可能使编译器误用寄存器,造成程序行为异常。
Q&A
C语言中如何嵌入汇编指令?
使用GCC提供的asm关键字,基本语法为asm(汇编指令),例如asm("mrs x0, PMCR_EL0");。基本asm块隐式是volatile的,但GCC不解析汇编指令。
扩展asm的语法结构是什么?
扩展asm语法为:asm qualifiers (AssemblerTemplate : output_constraint (C lvalue) : input_constraint (C expression) : Clobbers)。它允许从汇编中读写C变量,并执行跳转到C标签。
内联汇编中约束“r”和“=r”有什么区别?
“r”表示计算结果可以存储在通用寄存器中,通常用于输入约束;“=r”意思相同,但只写寄存器,通常用于输出约束。
为什么在内联汇编中需要使用clobber列表?
因为GCC无法理解汇编指令,不知道汇编代码会改变哪些寄存器。如果汇编有副作用(如修改其他寄存器),必须用clobber列表显式列出受影响的寄存器,避免GCC误用这些寄存器。
能否举例说明扩展asm如何读写C变量?
例如,将C表达式存入寄存器:asm volatile("msr pmintenset_el1, %0" : : "r" ((u64)(0 << 31))); 读取系统寄存器到C变量:asm("mrs %0, PMCR_EL0" : "=r" (f)); 其中f是long long变量。
基本asm和扩展asm的主要区别是什么?
基本asm对周围的C语法一无所知,不能读写C变量;扩展asm可以读写C变量,并支持跳转到C标签,语法更复杂,包含输出、输入约束和clobber列表。