以一个简单的例程开始。

下面的汇编语句可以像其它任何 C 语句一样出现在你的代码中。

/* example 1: NOP */
asm("mov r0,r0");

它将寄存器 r0 中的内容赋值给 r0。换句话说,它什么也没做。这种语句叫做 NOP(无操作)语句,通常用于实现一个极短的延时功能。

先等等!在将这个例程添加到你的 C 代码之前,请选继续阅读并学习本文后续部分,因为这段代码可能并不像我们所期望那样工作。

在内嵌汇编中使用汇编指令的方法与在纯汇编程序中使用汇编指令的方法一样。可以在一条 asm 语句中写多个汇编指令。但是为了增加程序的可读性,最好将每一条汇编指令单独放一行。

/* example 2: */
asm(
"mov     r0, r0\n\t"
"mov     r0, r0\n\t"
"mov     r0, r0\n\t"
"mov     r0, r0"
);

换行符和制表符的使用可以使得指令列表看起来变得美观。你第一次看时可能觉得有点怪异,但是当 C 编译器编译 C 语句的时候,它就是按照上面(换行和制表)的格式生成汇编代码的。到目前为止,内嵌汇编指令和你写的纯汇编程序中的代码没什么区别。但事实上,对比其它的 C 语句,asm 语句对常量、寄存器的处理是不一样的。通用的内嵌汇编模版是这样的:

asm(
    code 
    : 输出操作数列表
    : 输入操作数列表
    : clobber列表
    );

sam 语句的第二部分(输出操作数列表)和第三部分(输入操作数列表)是可选的,它们在汇编语言与 C 语言之间提供一个桥梁作用。第三个部分(clobbers 列表)也是可选的,我们将在随后介绍。

再看另一个例程,一个 C 整型变量,向右移一位,并将结果保持到另一个整型变量中。

/* example 3: Rotating bits example */
asm(
    "mov %[result], %[value], ror #1" 
    : [result] "=r" (y) 
    : [value] "r" (x)
    );

一条 asm 语句被冒号分为四个部分:

  1. 使用字符串字面值描述的汇编指令:
mov %[result], %[value], ror #1
  • 可选输出操作数列表。每个条目由方括号内的符号名、约束字符串、圆括号内的 C 表达式三部分组成:
    [result] "=r" (y)
  • 可选的输入操作数列表。其语法与输出操作数列表的语法相同。再次声明,这是可选的,且在我们的例程中只使用了一个操作数。
    [value] "r" (x)
  • 可选的 clobber 寄存器列表。该列表在本例程中被忽略。

只包含汇编指令的 asm 汇编叫做基本内联汇编,包含有可选部分的 asm 汇编叫做扩展内联汇编。

就像上面的 NOP 例程一样,如果尾部部分不使用,可以省略。但是需要注意,如果某一部分未使用,但是它后面的部分被使用了,那么必须将该部分空出。下面的例程用于设置 ARM CPU 的当前程序状态寄存器(CPSR),它使用了输入操作数,但没有使用输出操作数:

asm(
    "msr cpsr,%[ps]" 
    : 
    : [ps]"r"(status)
    );

即使不使用汇编代码,代码部分也要保留空字符串。在下面的例程中,创建了一个特殊的 clobber,以告诉编译器内存环境可能已改变。再次声明,我们在后面讨论代码优化的时候会解释 clobber 列表。

asm("":::"memory");

为了增加程序的可读性,你可以插入空白、增加新行甚至添加 C 语言注释:

asm("mov    %[result], %[value], ror #1"

           : [result]"=r" (y) /* Rotation result. */
           : [value]"r"   (x) /* Rotated value. */
           : /* No clobbers */
    );

在代码部分,你可以通过百分号%和符号名来引用操作数。这将引用操作数列表中的同名实体。

在移位例程中:
%[result] 引用了输出操作数中的 C 变量 y;
%[value] 引用了输入操作数中的 C 变量 x。

符号操作的名字使用了独立的命令空间,这意味着本语句的符号名与其它任何符号表不相关。简单一点就是说你不必关心使用的符号名在 C 代码中已经使用了。不过,在同一条 asm 语句中的不同符号的名字不能相同。

如果你看到过早期的 C 代码,循环移位的例子必须要这么写:

asm(
    "mov %0, %1, ror #1" 
    : "=r" (result) 
    : "r" (value)
    );

操作数的引用是通过百分号加数字实现的,其中 %0 引用第一个操作数,%1 引用第二个操作数,以此递推。最新的 GCC 发行版中依然支持该格式,但是该格式很容易让人犯错并使代码难以维护。试想一下,如果你写了大量的汇编指令,但是你想插入一个新的输出操作数,你必须手工修改操作数的引用编号。