指令顺序

开发者总是自以为源代码中指定的指令顺序与最终的指令顺序一致。这种写法是错误的,并导致难以查找bug。实际上,优化器会像优化 C 语句那样优化汇编语句。如果有可能,指令的顺序可能会重排。

“优化 C 代码”一节对此进行了详细讨论并提供了解决方案。

定义变量作为指定的寄存器

即使将一个变量强制赋值给了一个指定的寄存器,代码运行的结果也可能不是我们所期望的。考虑如下片段:

int foo(int n1, int n2) {
  register int n3 asm("r7") = n2;
  asm("mov r7, #4");
  return n3;
}

编译器被指示使用 r7 作为局部变量 n3,且使用参数 n2 进行初始化。接着在内联汇编语句中将 r7 设为 4,最后再返回。然后,这完全错了!一定要记住,编译器不知道内联汇编中发生了什么,但是优化器对 C 代码很聪明,将产生如下汇编代码:

foo:
  mov r7, #4
  mov r0, r1
  bx  lr

返回的结果不是 r7,而是 n2 的值。n2 会在我们的函数中传递给寄存器 r1。发生了什么?尽管最终的代码中包含了我们的内联汇编语句,C 代码优化器认为完全不需要使用 n3。它直接返回了参数 n2 。

仅仅将一个变量分配一个固定的寄存器不意味着 C 编译器将使用这个变量。我们仍然需要告诉编译器,在内联汇编的操作数中,有一个变量被修改了。对于给定了例程,我们需要在 asm 语句的输出操作数中做扩展:

asm("mov %0, #4" : "=l" (n3));

现在,C 编译器知道 n3 被修改了,并将产生我们期望的结果:

foo:
  push {r7, lr}
  mov  r7, #4
  mov  r0, r7
  pop  {r7, pc}

在 thumb 状态执行

需要注意,依赖于所给定的编译选项,编译器可能会切换到 thumb 状态。使用在 thumb 状态时无效的内联汇编指令将导致隐藏的编译错误。

汇编代码尺寸

在大多数情况下,编译器能正确的判断汇编指令的尺寸,但是当有汇编宏的时候是例外。因此最好避免之。

你可能会感到困惑:是汇编语言宏,不是 C 预处理宏。使用后者更好。

标签

在汇编指令中,你可以使用标签来达到跳转的目的。不过,你不能由一个汇编指令调整到另一个汇编指令。优化器只知道这些跳转将产生坏代码。

What ? 没明白!

预处理宏

内联汇编指令不能包含预处理宏,因为对于预处理器而言,这些指令仅仅是字符常量。