指令顺序
开发者总是自以为源代码中指定的指令顺序与最终的指令顺序一致。这种写法是错误的,并导致难以查找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 ? 没明白!
预处理宏
内联汇编指令不能包含预处理宏,因为对于预处理器而言,这些指令仅仅是字符常量。