如果我编译一个空的C函数
void nothing(void)
{
}
在MacOS上使用gcc -O2 -S
(和clang
),它会生成:
_nothing:
pushq %rbp
movq %rsp, %rbp
popq %rbp
ret
为什么gcc
除了ret
之外没有删除所有内容?这似乎是一个简单的优化,除非它真的做了一些事情(似乎不是,对我来说)。此模式(在开头按下/移动,在结尾处弹出)在其他非空函数中也可见,其中rbp
未被使用。
在Linux上使用更新的gcc
(4.4.5)我只看到了
nothing:
rep
ret
为什么rep
?非空函数中不存在rep
。
答案 0 :(得分:3)
为什么代表?
原因解释in this blog post。简而言之,直接跳转到单字节ret
指令会破坏某些AMD处理器上的分支预测。而不是在nop
之前添加ret
,而是添加了一个无意义的前缀字节以保存指令解码带宽。
非空函数中没有代表。
引用我链接到的博客文章:“[rep ret
]优先于简单的ret
,当它是任何类型的分支的目标时,条件({{ 1}})或无条件(jne/je/...
)“。
在空函数的情况下,jmp/call/...
将成为ret
的直接目标。在非空函数中,它不会是。
为什么gcc除了ret之外不会删除所有内容?
即使您已指定call
,某些编译器也可能不会省略帧指针代码。至少使用gcc,您可以使用-O2
选项明确告诉编译器省略它们。
答案 1 :(得分:2)
正如这里所解释的:http://support.amd.com/us/Processor_TechDocs/25112.PDF,使用了一个双字节的近返回指令(即rep ret
),因为在某些情况下,某些amd64处理器上的某些单字节返回值可能会被错误预测就像这个。
如果您使用gcc定位的处理器,您可能会发现可以生成单字节ret
。 -mtune=nocona
为我工作。
答案 2 :(得分:1)
我怀疑很早,你的最后一个代码是一个错误。正如johnfound所说。第一个代码是因为所有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。
答案 3 :(得分:-3)
也就是说,因为即使是所谓的“优化编译器”也太笨了,无法生成总是很好的机器代码。
他们无法生成比创作者生成更好的代码。
只要一个空函数是无意义的,他们可能根本就没有费心去优化它甚至检测这种非常特殊的情况。
虽然,单个“rep”前缀可能是一个bug。在没有字符串指令的情况下使用它什么都不做,但无论如何,在一些较新的CPU中它理论上可能会导致异常。 (恕我直言)