C 如何将输入传递到扩展asm?

C 如何将输入传递到扩展asm?,c,gcc,x86-64,inline-assembly,att,C,Gcc,X86 64,Inline Assembly,Att,考虑一下我的代码 我想让循环的迭代次数成为一个C变量,所以我在阅读后尝试了以下内容 不幸的是,它无法编译,并因以下错误而中断 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

考虑一下我的代码

我想让循环的迭代次数成为一个C变量,所以我在阅读后尝试了以下内容

不幸的是,它无法编译,并因以下错误而中断

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

如果使用扩展的汇编程序模板(带有输入、输出、缓冲等的模板),则需要在模板内的寄存器名上预先附加一个
%
<在这种情况下,代码>%%rcx。这将解决与此错误相关的问题:

错误:“asm”无效:在%字母后缺少操作数

这将提出一个新问题。您将收到类似以下内容的错误:

“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));
mov    $0x5f5e100,%ebx
startofloop:
sub    $0x1,%ebx
jne    startofloop
asm volatile("startofloop: ; "
    "sub $0x1, %0; "
    "jne startofloop; ":"+rm"(count): );
或者,您可以选择使用
“ri”(count)
的输入操作数。这将允许编译器选择寄存器或立即数。在更高的优化级别(
-O1
-O2
)上,可能会确定在这种情况下,
计数保持不变(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");
int count=100000000
int dummy;
asm volatile("mov %1, %0;"                         
    "startofloop: ; "                    
    "sub $0x1, %0; "                   
    "jne startofloop; ":"=rm"(dummy): "ri"(count));
现在GCC知道它不能依赖于RCX中的值在执行模板之前和之后是相同的值

您可以让GCC选择可用的内容,而不是使用固定寄存器作为内部计数器。这样做将意味着我们不再需要打击者。您可以创建一个虚拟变量(临时变量),该变量可用于计数。为了避免这段代码被完全优化,我们可以在汇编程序模板上使用
volatile
属性。当汇编器模板没有输出操作数时,这不是必需的。这样的代码可以工作:

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub $0x1, %%ecx; "                   
    "jne startofloop; ":: "ri"(count) : "rcx");
int count=100000000
int dummy;
asm volatile("mov %1, %0;"                         
    "startofloop: ; "                    
    "sub $0x1, %0; "                   
    "jne startofloop; ":"=rm"(dummy): "ri"(count));
=rm
输出约束表示此操作数可以使用内存位置或寄存器。将选择权交给编译器,就有机会生成更好的代码。在
-O1
的优化级别,您可能会发现生成的代码如下所示:

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub $0x1, %%ecx; "                   
    "jne startofloop; ":: "r"(count));
mov    $0x5f5e100,%ebx
startofloop:
sub    $0x1,%ebx
jne    startofloop
asm volatile("startofloop: ; "
    "sub $0x1, %0; "
    "jne startofloop; ":"+rm"(count): );
在本例中,编译器选择使用立即数操作数进行计数($0x5f5e100=$100000000)。
dummy
变量被优化到寄存器EBX

您还可以使用其他技巧来改进模板。您可以在


您的代码似乎保留了变量
count
中的值。如果在执行模板之前,
count
不要求具有相同的值,则可以对输入和输出使用
count
。该代码可能如下所示:

asm("mov %0, %%ecx;"                         
    "startofloop: ; "                    
    "sub $0x1, %%ecx; "                   
    "jne startofloop; ":: "r"(count));
mov    $0x5f5e100,%ebx
startofloop:
sub    $0x1,%ebx
jne    startofloop
asm volatile("startofloop: ; "
    "sub $0x1, %0; "
    "jne startofloop; ":"+rm"(count): );
+rm
表示输出操作数也用作输入操作数。在这种情况下,完成时计数应始终为零


如果使用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就这个主题写了一篇文章。

如果使用扩展汇编程序模板(带有输入、输出、缓冲等的模板),那么您需要在寄存器名上预先添加一个额外的
%
。也许可以试试
%%rcx
。由于您覆盖了RCX,并且它没有显示为输出操作数,因此需要在clobber列表中列出它。@MichaelPetch,clobber上的要点很好。不幸的是,当我执行
%%rcx
时,我得到了'movq'
操作数类型不匹配。是的,这可能是因为
计数
是一个32位整数,并且汇编程序模板选择了一个32位寄存器(如
eax
)进行替换。您可以在
%0
之后放置
q
,这样类似于
movq%q0,%%rcx@MichaelPetch,这似乎起到了作用。谢谢另请参阅,以获取有关使用GNU C内联asm生成不易吸收的代码的信息(例如,在模板内部使用
movq
,而不是使用操作数约束要求编译器为您设置)。在尽可能少地约束优化器的情况下,很难使其正确,而且它仍然无法通过内联asm进行持续传播。因此,通常最好调整C语言,使编译器保持良好的asm,而不是使用内联asm。这是一篇优秀的论文。