内联汇编约束修饰符 = 和 +
Posted
技术标签:
【中文标题】内联汇编约束修饰符 = 和 +【英文标题】:Inline assembly constraint modifiers = and + 【发布时间】:2013-05-22 15:09:48 【问题描述】:我编写了一个包含内联汇编代码的简单程序。 我的代码只是添加变量 a 和 b 并返回 结果是 b。
让我困惑的是为什么下面的代码会生成 此指令 movl 28(%esp), %ecx.
我不完全理解修饰符 + 和 = 在输入和输出列表中的作用。 因此,如果您能对此有所了解,我们将不胜感激。
#include <cstdio>
int main( int argc , char ** argv )
int a = 2, b = 7;
__asm__
(
"addl %1,%0;"
:"+r"(b)
:"r"(a), "r"(b)
);
printf("b = %d\n",b);
return 0;
movl $2, 24(%esp) movl $7, 28(%esp) movl 24(%esp), %edx movl 28(%esp), %ecx movl 28(%esp), %eax addl %edx,%eax movl %eax, 28(%esp)
我接下来要展示的内容是错误的。但这是为了让我更好地了解 GCC 中发生的事情。
好的,现在我从 +r 更改为 =r。这是 GCC 生成的汇编代码。
#include <cstdio>
int main( int argc , char ** argv )
int a = 2, b = 7;
__asm__
(
"addl %1,%0;"
:"=r"(b)
:"r"(a), "r"(b)
);
printf("b = %d\n",b);
return 0;
movl $2, 24(%esp) movl $7, 28(%esp) movl 24(%esp), %eax movl 28(%esp), %edx addl %eax,%eax; movl %eax, 28(%esp)
现在输出为 4,这是错误的。 我的问题是为什么使用 "=r" GCC 决定为 b 重用寄存器 eax,如图所示 这条指令addl %eax,%eax;
【问题讨论】:
【参考方案1】:让我困惑的是为什么下面的代码会生成这条指令
movl 28(%esp), %ecx
因为您已将b
列为位于两个单独的输入寄存器中;第一部分中的+
表示程序集读取和修改寄存器。所以它被加载到两个寄存器中,即使程序集不使用第二个。
程序集应该是:
"addl %1,%0;" : "+r"(b) : "r"(a)
我不完全理解修饰符 + 和 = 在输入和输出列表中的作用。
+
表示寄存器被读写,因此在汇编开始之前它必须具有变量的值。 =
表示它只是被写入,并且在组装之前可以具有任何值。
详情请参阅documentation。
我的问题是为什么使用 "=r" GCC 决定将寄存器 eax 用于 b,如本指令所示 addl %eax,%eax;
因为现在你的约束是错误的。您告诉编译器您只写入addl
指令的第二个操作数(%0
),因此它假定它可以使用与输入之一相同的寄存器。事实上,该操作数也是addl
的输入。然后你仍然告诉它你需要在程序集不使用的单独寄存器中的b
的第二个副本。
如上所述,在第一个(输出)列表中使用"+r"(b)
表示%0
是b
,用于输入和输出,在第二个(输入)列表中使用"r"(a)
表示%1
是a
并且仅用于输入。不要放入第三个寄存器,因为程序集中没有 %2
。
【讨论】:
感谢您的回答。我开始理解这个概念。但我还有另一个例子。我将很快用一个新示例更新我的帖子。 @takwing:我已经更新了新问题。简短的回答是:如果寄存器约束与程序集所做的不匹配,那么寄存器分配将是虚假的。 非常感谢您的回答。我很清楚这一点。至于文档,我一遍又一遍地阅读,但我仍然没有理解整个概念。这是文档中关于两个修饰符的说明: =' 表示此操作数对于此指令是只写的:先前的值被丢弃并被输出数据替换。 ‘+’ 表示该操作数既可以被指令读取也可以被指令写入。让我感到困惑和仍然困惑的是“这个指令”这个词的使用。但是,在 asm() 内部可以有一系列汇编指令。 @takwing:是的,这有点令人困惑。 “本说明书”是指整个组装过程;编译器此时不知道单独的汇编指令。它只是根据约束分配寄存器等,将它们替换为汇编字符串,然后将其与生成的其余代码一起汇编。 我明白了:) 所以编译器对 asm() 中的任何内容都一无所知,除了输入、输出和损坏的寄存器列表?【参考方案2】:它非常简单——+
表示输入和输出,而=
表示仅输出。所以当你说:
asm("addl %1,%0;" : "+r"(b) : "r"(a), "r"(b));
你有三个操作数。一个输入/输出寄存器(%0
)初始化为b
,并将其输出放入b
,两个输入寄存器(%1
和%2
)分别初始化为a
和b
。现在你从不使用%2
,但无论如何它都在那里。您可以在生成的代码中看到它:
movl 24(%esp), %edx
movl 28(%esp), %ecx
movl 28(%esp), %eax
addl %edx,%eax
movl %eax, 28(%esp)
所以编译器使用%eax
代表%0
,%edx
代表%1
和%ecx
代表%2
。这三个都在内联代码之前加载,%0
在之后写回。
当你这样做时:
asm("addl %1,%0;" : "=r"(b) : "r"(a), "r"(b));
现在%0
只是输出(不是输入)。所以编译器可以产生:
movl 24(%esp), %eax
movl 28(%esp), %edx
addl %eax,%eax;
movl %eax, 28(%esp)
将%eax
用于%0
和%1
和%edx
用于%2
。
获得你想要的东西的另一种方式是:
asm("addl %1,%0;" : "=r"(b) : "r"(a), "0"(b));
在%2
上使用0
约束意味着编译器必须将其放在与
%0
所以最终生成:
movl 24(%esp), %edx
movl 28(%esp), %eax
addl %edx,%eax
movl %eax, 28(%esp)
将%eax
用于%0
和%2
,将%edx
用于%1
【讨论】:
【参考方案3】:您不应该使用"r"
重复b
- 使用"0"
(输出参数编号)。现在编译器正在将b
加载到未使用的%ecx
中。
【讨论】:
在 "r"(b) 中重复 r 意味着什么?为什么会导致 GCC 将 b 加载到不同的寄存器中,在这种情况下是 ECX?【参考方案4】:“我写了一个包含内联汇编代码的简单程序。”
"我的代码只是简单地将变量 a 和 b 添加并在 b 中返回结果。 哈哈哈哈哈哈”
在哪里?
mov ax,2
mov bx,3
add bx,ax
如果你愿意,可以叫我爱因斯坦……
这让我想起了“恶搞”采访
阅读和哭泣
http://www-users.cs.york.ac.uk/susan/joke/cpp.htm
【讨论】:
以上是关于内联汇编约束修饰符 = 和 +的主要内容,如果未能解决你的问题,请参考以下文章