从我的earlier question开始考虑此代码。
int main(){
asm("movq $100000000, %rcx;"
"startofloop: ; "
"sub $0x1, %rcx; "
"jne startofloop; ");
}
我想将循环的迭代次数变为C变量,所以在阅读this document之后我尝试了以下内容。
int main(){
int count = 100000000;
asm("movq %0, %rcx;"
"startofloop: ; "
"sub $0x1, %rcx; "
"jne startofloop; ":: "r"(count));
}
不幸的是,这无法编译,并且因以下错误而中断。
asm_fail.c: In function ‘main’:
asm_fail.c:3:5: error: invalid 'asm': operand number missing after %-letter
asm("movq %0, %rcx;"
^
asm_fail.c:3:5: error: invalid 'asm': operand number missing after %-letter
将C变量的值传递给程序集的正确方法是什么?
答案 0 :(得分:6)
如果使用扩展汇编程序模板(带有输入,输出,clobbers等的模板),则需要在模板内的寄存器名称前加一个%
。在这种情况下%%rcx
。这将解决与此错误相关的问题:
错误:'asm'无效:%-letter
后缺少操作数
这会带来一个新问题。您将收到类似于以下错误:
'movq'
的操作数类型不匹配
问题是"r"(count)
输入约束告诉编译器它应该选择一个包含count
中值的寄存器。由于count被定义为int
类型,因此它将选择一个32位寄存器。为了论证,假设它选择了 EAX 。替换后,它会尝试生成此指令:
movq %eax, %rcx
您不能使用movq
将32位寄存器的内容移动到64位寄存器,从而导致错误。更好的选择是使用 ECX 作为目标,以便两者具有相同的类型。修改后的代码如下:
asm("mov %0, %%ecx;"
"startofloop: ; "
"sub $0x1, %%ecx; "
"jne startofloop; ":: "r"(count));
或者您可以选择使用"ri"(count)
的输入操作数。这将允许编译器选择寄存器或立即值。在更高的优化级别(-O1
,-O2
),在这种情况下,它可能会确定count
保持不变(100000000)并生成如下代码:
mov $100000000, %ecx
startofloop:
sub $0x1, %ecx
jne startofloop
不是被迫将100000000放入寄存器并将其复制到 ECX ,而是可以使用立即值。
模板中的一个严重问题是您销毁 ECX 的内容,但 GCC 不知道这一点。 GCC 实际上并不解析模板中的指令以确定代码的作用。它不知道你已经破坏了 ECX 。编译器可能依赖于模板之前和之后具有相同值的 ECX 。如果销毁未在输出操作数中引用的寄存器,则必须在clobber列表中明确列出它。像这样的东西会起作用:
asm("mov %0, %%ecx;"
"startofloop: ; "
"sub $0x1, %%ecx; "
"jne startofloop; ":: "ri"(count) : "rcx");
现在 GCC 知道它不能依赖 RCX 中的值在执行模板之前和之后的相同值。
您可以使用 GCC 来选择可用的内容,而不是使用固定寄存器作为内部计数器。这样做意味着我们不再需要clobber了。您可以创建一个可用于计数的虚拟变量(临时)。为了避免这些代码被完全优化,我们可以在汇编程序模板上使用volatile
属性。当汇编程序模板没有输出操作数时,这不是必需的。像这样的代码可以工作:
int count=100000000
int dummy;
asm volatile("mov %1, %0;"
"startofloop: ; "
"sub $0x1, %0; "
"jne startofloop; ":"=rm"(dummy): "ri"(count));
=rm
输出约束表示存储器位置或寄存器可用于此操作数。选择编译器可以生成更好的代码。在-O1
的优化级别,您可能会发现生成的代码如下所示:
mov $0x5f5e100,%ebx
startofloop:
sub $0x1,%ebx
jne startofloop
在这种情况下,编译器选择使用立即操作数进行计数($ 0x5f5e100 = $ 100000000)。 dummy
变量已优化到寄存器 EBX 。
您可以采取其他技巧来改进模板。可以在GNU documentation
中阅读有关扩展汇编程序模板的更多信息您的代码似乎保留了变量count
中的值。如果count
在执行模板之前不需要具有相同的值,则可以对输入和输出使用count
。该代码可能如下所示:
asm volatile("startofloop: ; "
"sub $0x1, %0; "
"jne startofloop; ":"+rm"(count): );
+rm
表示输出操作数也用作输入操作数。在这种情况下,count
在完成时应始终为零。
如果您使用 GCC -S
选项输出生成的汇编代码,那么您可能希望更改模板以使输出看起来更清晰。而不是使用;
(分号)代替使用\n\t
。这会将汇编程序模板拆分为多行并添加缩进。一个例子:
asm volatile("mov %1, %0\n\t"
"startofloop:\n\t"
"sub $0x1, %0\n\t"
"jne startofloop\n\t":"=rm"(dummy): "ri"(count));
一般来说,除非别无选择,否则不应使用内联汇编程序模板。在 C 中对其进行编码,并引导编译器输出所需的汇编程序,或者在需要时使用编译器内在函数。内联汇编程序应作为最后的手段,或者如果您的作业需要它。 David Wohlferd在这个问题上写了Wiki article。