最小的教育例子
在这里,我提供了一个最小的教育示例,试图使https://stackoverflow.com/a/15819941/895245提到的内容更清晰。
这个具体的代码在实践中当然没有用,单lea 1(%q[in]), %out
条指令可以更高效地实现,只是一个简单的教育例子。
主程序
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in = 1;
uint64_t out;
__asm__ (
"mov %[in], %[out];" /* out = in */
"inc %[out];" /* out++ */
"mov %[in], %[out];" /* out = in */
"inc %[out];" /* out++ */
: [out] "=&r" (out)
: [in] "r" (in)
:
);
assert(out == 2);
}
编译并运行:
gcc -ggdb3 -std=c99 -O3 -Wall -Wextra -pedantic -o main.out main.c
./main.out
这个程序是正确的并且断言通过了,因为强制编译器为和&
选择不同的寄存器。in
out
这是因为&
告诉编译器in
可能在out
写入后使用,这里实际上就是这种情况。
因此,唯一不会错误修改in
的方法是将in
和out
放在不同的寄存器中。
拆解:
gdb -nh -batch -ex 'disassemble/rs main' main.out
包含:
0x0000000000001055 <+5>: 48 89 d0 mov %rdx,%rax
0x0000000000001058 <+8>: 48 ff c0 inc %rax
0x000000000000105b <+11>: 48 89 d0 mov %rdx,%rax
0x000000000000105e <+14>: 48 ff c0 inc %rax
这表明 GCC 选择了rax
forout
和rdx
for in
。
但是,如果我们删除它&
,则行为是未指定的。
在我的测试系统中,断言实际上失败了,因为编译器试图最小化寄存器的使用,并编译为:
0x0000000000001055 <+5>: 48 89 c0 mov %rax,%rax
0x0000000000001058 <+8>: 48 ff c0 inc %rax
0x000000000000105b <+11>: 48 89 c0 mov %rax,%rax
0x000000000000105e <+14>: 48 ff c0 inc %rax
因此rax
同时用于in
和out
。
这样做的结果out
是增加了两次,并且等于3
而不是2
最后。
在 Ubuntu 18.10 amd64、GCC 8.2.0 中测试。
更实际的例子