在 C/C++ 代码中嵌入汇编代码

2024-04-10 C++

最近在学习原子变量底层实现的过程中,看到了一些在 C/C++ 中嵌入汇编代码的例子,因此学习了一下相关的语法规则,有了这篇总结,便于将来查阅。

注意:本文仅适用于 GCC,并且 x86-64 汇编采用 AT&T 语法。MSVC 内嵌汇编的语法可能有所不同。

1. 基本语法

在 C/C++ 的语言规范中,有一个 asm 关键字,它便是用来在 C/C++ 代码中内嵌汇编代码的。其基本语法如下:

asm("assembly code");

__asm__("assembly code");

可以认为上面两种语法没有差别,因此后文都采取更简洁的 asm 形式。

一个完整的例子:

#include <stdio.h>

int main() {
  /* 把 10 个 20 加起来,将结果存储到 eax 寄存器中。 */
  asm("movl $10, %eax;"
      "movl $20, %ebx;"
      "addl %ebx, %eax;");

  return 0;
}

可以看到,基本就是只需要用 asm 把汇编代码包起来就可以了。

2. 扩展语法

除了能执行一些简单的机器指令外,更多时候我们需要在 C++ 和汇编之间进行「数据沟通」,即「在汇编代码中读写 C++ 代码中的变量」。此时就需要用到扩展语法,它允许我们指定每条指令的「输入输出」,其基本形式如下:

asm ("assembly code"
     : output operands                  /* optional */
     : input operands                   /* optional */
     : list of clobbered registers      /* optional */
);

即,在汇编后面用冒号 : 增加一些可选的输出操作数、输入操作数以及「会被操作搞乱(clobbered)的寄存器」。

接下来看几个例子就能明白了:

asm("movl %%eax, %0;" : "=r"(val));

这段代码的意思是eax 寄存器的内容存储到 val 变量中。更多语法细节:

  1. 这里额外指定了 1 个输出操作数——"=r"(val),前面的字符串 "=r" 被称为操作数约束,后面在括号里放一个 C++ 表达式,代表某个变量:
    • r 约束的意思是 val 操作数只能被存储在通用寄存器中;
    • = 的意思是该操作数会被写入
  2. %0 就代指第一个操作数(从 0 开始)。
  3. 这里引用寄存器的方式变成了使用 2 个 %:%%eax,这是为了与 %0 做出区分。

另一个更复杂的例子:对两个整数求和,将结果存储在第三个数中:

#include <cstdio>

int main() {
  int a = 2;
  int b = 3;
  int y;

  asm("movl %1, %%eax;"
      "movl %2, %%ebx;"
      "addl %%ebx, %%eax;"
      "movl %%eax, %0;"
      : "=r"(y)        /* output */
      : "r"(a), "r"(b) /* inputs */
      : "%eax", "%ebx" /* clobbered registers */
  );

  printf("y=%d\n", y); 
  return 0;
}

运行结果:

y=5

如果能看懂上面这段代码,那么绝大多数 C++ 内嵌汇编应该都能读懂了。如果想了解更多用法,请参考 GCC 官方文档:Using Assembly Language with C (Using the GNU Compiler Collection (GCC)).

参考资料

  1. Using Inline Assembly in C/C++ - CodeProject
  2. Using Assembly Language with C (Using the GNU Compiler Collection (GCC))