mer*_*011 3 c gcc x86-64 inline-assembly att
从我之前的问题中考虑这段代码.
int main(){
asm("movq $100000000, %rcx;"
"startofloop: ; "
"sub $0x1, %rcx; "
"jne startofloop; ");
}
Run Code Online (Sandbox Code Playgroud)
我想将循环的迭代次数变为C变量,所以在阅读本文后我尝试了以下内容.
int main(){
int count = 100000000;
asm("movq %0, %rcx;"
"startofloop: ; "
"sub $0x1, %rcx; "
"jne startofloop; ":: "r"(count));
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这无法编译,并打破以下错误.
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
Run Code Online (Sandbox Code Playgroud)
将C变量的值传递给程序集的正确方法是什么?
如果使用扩展汇编程序模板(具有输入,输出,clobbers等的模板),则需要%在模板内的寄存器名称前加一个额外的模板.%%rcx在这种情况下.这将解决与此错误相关的问题:
错误:'asm'无效:%-letter后缺少操作数
这将带来一个新问题.您将收到类似于以下内容的错误:
'movq'的操作数类型不匹配
问题是"r"(count)输入约束告诉编译器它应该选择一个包含值的寄存器count.由于count被定义为一种int类型,它将选择一个32位寄存器.为了论证,假设它选择了EAX.替换后,它会尝试生成此指令:
movq %eax, %rcx
Run Code Online (Sandbox Code Playgroud)
您不能使用movq将32位寄存器的内容移动到64位寄存器,从而导致错误.更好的选择是使用ECX作为目标,以便两者具有相同的类型.修改后的代码如下所示:
asm("mov %0, %%ecx;"
"startofloop: ; "
"sub $0x1, %%ecx; "
"jne startofloop; ":: "r"(count));
Run Code Online (Sandbox Code Playgroud)
或者你可以选择使用输入操作数"ri"(count).这将允许编译器选择寄存器或立即值.在更高的优化级别(-O1,-O2),它可能会在这种情况下确定count保持不变(100000000)并生成如下代码:
mov $100000000, %ecx
startofloop:
sub $0x1, %ecx
jne startofloop
Run Code Online (Sandbox Code Playgroud)
而不是被迫将100000000放入寄存器并将其复制到ECX,它可以使用立即值.
模板中的一个严重问题是您破坏了ECX的内容,但GCC不知道这一点.GCC实际上并不解析模板中的指令来确定代码的作用.它不知道你有破坏ECX.编译器可能依赖于在模板之前和之后具有相同值的ECX.如果销毁未在输出操作数中引用的寄存器,则必须在clobber列表中明确列出它.像这样的东西会起作用:
asm("mov %0, %%ecx;"
"startofloop: ; "
"sub $0x1, %%ecx; "
"jne startofloop; ":: "ri"(count) : "rcx");
Run Code Online (Sandbox Code Playgroud)
现在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));
Run Code Online (Sandbox Code Playgroud)
的=rm输出约束说,无论是存储位置或寄存器可以用于此操作数.选择编译器可以生成更好的代码.在优化级别,-O1您可能会发现生成的代码如下所示:
mov $0x5f5e100,%ebx
startofloop:
sub $0x1,%ebx
jne startofloop
Run Code Online (Sandbox Code Playgroud)
在这种情况下,编译器选择使用立即操作数进行计数($ 0x5f5e100 = $ 100000000).该dummy变量优化到寄存器EBX.
您可以采取其他技巧来改进模板.可以在GNU文档中阅读有关扩展汇编程序模板的更多信息
您的代码似乎保留了变量中的值count.如果count在执行模板之前不需要具有相同的值,则可以使用count输入和输出.该代码可能如下所示:
asm volatile("startofloop: ; "
"sub $0x1, %0; "
"jne startofloop; ":"+rm"(count): );
Run Code Online (Sandbox Code Playgroud)
+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));
Run Code Online (Sandbox Code Playgroud)
一般来说,除非别无选择,否则不应使用内联汇编程序模板.在C中编写代码并指导编译器输出所需的汇编程序,或者在需要时使用编译器内在函数.内联汇编程序应作为最后的手段,或者如果您的作业需要它.David Wohlferd写了一篇关于这个主题的维基文章.
| 归档时间: |
|
| 查看次数: |
716 次 |
| 最近记录: |