如何将输入传递给扩展的asm?

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变量的值传递给程序集的正确方法是什么?

Mic*_*tch 6

如果使用扩展汇编程序模板(具有输入,输出,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写了一篇关于这个主题的维基文章.

  • 一篇优秀的论文.我可能添加的其他几点是1)考虑使用符号名称使汇编代码更容易阅读.2)内联asm很有趣,有趣,具有挑战性,有教育意义,而且在生产代码中也是个坏主意.我总是说,因为*这个*家伙可能还没有听过.3)"cc"clobber.4)由于可能没有使用`dummy`,所以volatile可能是一个好主意,让优化器不要将整个事件作为死代码丢弃. (2认同)