5

如果我编译一个空的 C 函数

void nothing(void)
{
}

在 MacOS 上使用gcc -O2 -S(and ),它会生成:clang

_nothing:
    pushq   %rbp
    movq    %rsp, %rbp
    popq    %rbp
    ret

为什么不gcc删除除ret? 除非它真的做某事(对我来说似乎不是),否则这似乎是一个容易进行的优化。这种模式(开始时推/移动,结束时弹出)在其他未使用的非空函数中也可见rbp

在 Linux 上使用更新的gcc (4.4.5)我只看到

nothing:
    rep
    ret

为什么reprep非空函数中不存在。

4

4 回答 4

3

为什么是代表?

原因在此博客文章中进行了解释。简而言之,直接跳转到单字节ret指令会扰乱某些 AMD 处理器上的分支预测。并且不是在nop之前添加一个,而是添加了ret一个无意义的前缀字节以节省指令解码带宽。

非空函数中不存在代表。

引用我链接到的博客文章: “当它是任何类型的分支、条件 ( ) 或无条件 ( ) 的目标时, [ rep ret] 优于简单的”retjne/je/...jmp/call/...
在空函数的情况下, theret将是 a 的直接目标call。在非空函数中,它不会。

为什么 gcc 不删除除 ret 之外的所有内容?

即使您指定了-O2. 至少使用 gcc,您可以使用该-fomit-frame-pointer选项明确告诉编译器忽略它们。

于 2013-08-05T12:24:06.727 回答
2

正如这里所解释的:http: //support.amd.com/us/Processor_TechDocs/25112.PDF,使用了一个两字节的近返回指令(即rep ret),因为在某些 amd64 处理器上我可能会错误地预测单字节返回在某些情况下,例如这种情况。

如果你摆弄 gcc 所针对的处理器,你可能会发现你可以让它生成一个 single-byte ret-mtune=nocona为我工作。

于 2013-08-05T07:15:59.973 回答
1

我很早就怀疑,您的最后一个代码是一个错误。正如约翰芬所说。第一个代码是因为所有 C 编译器必须始终遵循函数中的 _cdecl 调用约定(在英特尔中,抱歉,我不知道 AT&T 语法):

功能定义

_functionA:
push   rbp
mov    rbp, rsp
;Some function
pop    rbp
ret

在来电者中:

call _functionA
sub esp, 0 ; Maybe if it zero, some compiler can strip it

为什么 GCC 在不遵循时总是遵循 _cdecl 调用约定是无稽之谈,即编译器并不比高级汇编程序员更聪明。因此,它总是不惜一切代价遵循 _cdecl。

于 2013-08-05T06:49:57.753 回答
-3

也就是说,因为即使是所谓的“优化编译器”也太笨了,无法生成总是好的机器代码。

他们无法生成比他们的创建者让他们生成的代码更好的代码。

只要一个空函数是无意义的,他们可能根本就懒得去优化它,甚至没有检测到这种非常特殊的情况。

虽然,单个“rep”前缀可能是一个错误。在没有字符串指令的情况下使用它什么都不做,但无论如何,在一些较新的 CPU 中它理论上会导致异常。(恕我直言应该)

于 2013-08-05T06:32:19.063 回答