在c语言中使用汇编代码,为了实现更高效率或执行特定汇编指令。

语法格式

asm volatile("assembly code"
: output operands /* 可选 */
: input operands /* 可选 */
: list of clobbered registers /* 可选 */
);
  1. asm和volatile

    • asm 是内联汇编的关键字。
    • volatile 关键字告诉编译器不要优化这段汇编代码。
  2. 输出操作数

     格式为 :[constraints](C variable)
    
     constraints 是约束字符串,定义了输出操作数的类型和位置。
    (C variable)是 C 变量,用于存储汇编代码的输出。
    
  3. 输入操作数
    同输出操作数

  4. 列表的损坏寄存器
    这是一个可选部分,列出了在汇编代码中被修改的寄存器,告知编译器这些寄存器在汇编代码后可能包含不同的值。

e.g. :

#include <stdio.h>

void main(void) {
int a = 1;
int b;
asm volatile ("mv %0, %1" : "=r"(b) : "r"(a));
printf("b = %d\n", b);
}

这个例子中将a的值赋值给b并且打印输出了b的值。

constraints 约束字符串

约束字符与约束字符不可以连用,但约束字符和约束修饰符可以连用。

约束字符

  • r表明通用寄存器,编译器可以选择任意一个通用寄存器来存储操作数。
  • m表示内存地址,操作数必须是内存位置。
  • i表示立即数,操作数必须是立即数。

约束修饰符

  • =表示输出操作数,所操作的操作数可写。
  • +表示可读可写,所操作的操作数可读可写。
  • &该操作数要独占相应的寄存器,不要再将其分配给别人。

匹配约束

匹配约束使用数字来表示操作数与另一个操作数相同,使用这个约束时,两个操作数将使用相同的寄存器或内存地址。

e.g.

asm volatile("mv %0, %1" : "=r"(a) : "0"(b));

在这段代码中,表示ab使用相同的寄存器。

符号名

在内联汇编中,可以使用别名(也称为符号名)来提高代码的可读性。别名允许你为操作数指定一个更具描述性的名称,而不是依赖数字占位符(如 %0、%1 等)。

格式如下:

asm volatile(
"assembly code with %(alias)s"
: [alias1] "constraint" (variable1)
, [alias2] "constraint" (variable2)
: [alias3] "constraint" (variable3)
, [alias4] "constraint" (variable4)
: "clobbered registers"
);

在使用时,%(符号名)来进行调用。

e.g.

asm volatile("mv %(a), %(b)" : [a]"=r"(a) : [b]"0"(b));

list of clobbered registers

在 GCC 内联汇编中,clobbered registers 列表用于告诉编译器在执行内联汇编代码时会修改哪些寄存器。编译器会确保这些寄存器的值在汇编代码执行前被保存,并在汇编代码执行后恢复。这对于避免意外的寄存器值变化和潜在的错误非常重要。

e.g.

#include <stdio.h>

void main(void) {
int a = 5, b = 10, result;
asm volatile(
"add %0, %1, %2\n\t"
: "=r" (result) // 输出操作数
: "r" (a), "r" (b) // 输入操作数
: "t0" // clobbered 寄存器
);
printf("result = %d\n", result); // 输出 result = 15
}

在这段代码当中,防止t0被修改(实际根本不会被修改),写入了clobbered寄存器当中,在执行完汇编代码之前,会保存t0寄存器,执行之后,会恢复t0寄存器。

memory

如果你在clobbered加入memory这个特殊的描述符,用于告诉编译器,汇编代码会对内存进行读写操作,可能会影响到内存中的任何数据。这会让编译器知道,它不能依赖于寄存器中的数据,并且需要在汇编代码执行前将所有变量值刷新到内存中,以及在汇编代码执行后重新加载这些变量值。

当你使用 memory 作为 clobbered 描述符时,你告诉编译器以下几点:

输入操作数和输出操作数:编译器会确保在执行汇编代码之前,将所有输入操作数的值刷新到内存中,并在执行汇编代码后,从内存中重新加载所有输出操作数的值。
防止优化:编译器不会对涉及内存的指令进行重排序,因为它不能确定内联汇编代码具体修改了哪些内存地址。这有助于防止潜在的优化问题。