c语言 - 为什么 GCC 在使用 int foo asm( "ebx" ) 将 var 固定到寄存器时放置一个无操作的推送/弹出?



我正试图编写代码从寄存器读取,这是我到目前为止所拥有的:

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替换为另一个寄存器(例如eaxecx)时,它会产生更安全的代码(分别只有retmov 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块中关联了ebxreg]不符合编译器实际上向您承诺:唯一的保证是本地寄存器变量将在(在您的情况下)ebx 中,在asm中使用声明 .

除此之外,它只是一个局部变量。

您在序言和尾声中得到保存/恢复序列,因为Ebx是一个非易失性寄存器,这个函数使用这个寄存器。之后调度将在代码的后面移动推送。

如果您想简单地获取ebx中的内容,您可以使用asm,如

long reg; asm("mov %%ebx,%0" : "=r"(reg));

所以基本上push/pop只是在那里,因为它是一个被调用者保存的reg,然后后来的优化器传递将push移动到asm下面,所以它看起来不再完全像序言了。


1方括号中的上下文是我的。

相关内容

最新更新