我正试图编写代码从寄存器读取,这是我到目前为止所拥有的:
unsigned int readEBX(void) {
register unsigned int reg asm("ebx");
return reg;
}
函数看起来正常,但是它编译成一些奇怪的东西:
readEBX():
mov eax, ebx
push ebx
pop ebx
ret
为什么这个push-then-pop ebx?这不是毫无用处吗?当我将ebx
替换为另一个寄存器(例如eax
或ecx
)时,它会产生更安全的代码(分别只有ret
和mov eax, ecx; ret
)。
查看此示例Godbolt结果
由于您显式地告诉编译器您对寄存器感兴趣,因此编译器试图变得聪明。既然你想要观察寄存器,就不要告诉编译器,这样他就不会乱来了。
这对我来说是有效的(对mov
操作的顺序取模)
unsigned int readEBX(void) {
register unsigned int ret __asm__("eax");
__asm__ volatile("mov %%ebx, %0" : "=r"(ret));
return ret;
}
它只是确保ret
使用不同的寄存器,所以没有冲突。
我猜:
使用特定的寄存器声明变量是一个不被广泛使用的特性,所以不写就读它是一种极端情况中的极端情况。
可能声明了一个固定在被调用者保存的寄存器标志上的变量,该标志注册为函数使用的,导致gcc发出push/pop参数来保存/恢复它。
这是次优的asm,但是调用函数只是为了读取ebx也会浪费很多开销。这个函数在内联时应该没问题,除非它被内联到一个不使用ebx本身的函数中。在这种情况下,您应该使readEBX
成为一个宏,使用来自Jens回答的内联asm。
这是直接从上面链接的bug报告1:
中得到的奇数push/pop
序列的实际来源你的期望[在C中返回
reg
将返回ebx
的值,因为你在asm块中关联了ebx
和reg
]不符合编译器实际上向您承诺:唯一的保证是本地寄存器变量将在(在您的情况下)ebx 中,在asm中使用声明 .除此之外,它只是一个局部变量。
您在序言和尾声中得到保存/恢复序列,因为Ebx是一个非易失性寄存器,这个函数使用这个寄存器。之后调度将在代码的后面移动推送。
如果您想简单地获取ebx中的内容,您可以使用asm,如
long reg; asm("mov %%ebx,%0" : "=r"(reg));
所以基本上push/pop
只是在那里,因为它是一个被调用者保存的reg,然后后来的优化器传递将push
移动到asm下面,所以它看起来不再完全像序言了。
1方括号中的上下文是我的。