为什么32位C将所有函数自变量直接推入堆栈,而64位C将前6个参数放到寄存器中,而其余的则放到堆栈中呢?
因此32位堆栈如下所示:
...
arg2
arg1
return address
old %rbp
64位堆栈如下所示:
...
arg8
arg7
return address
old %rbp
arg6
arg5
arg4
arg3
arg2
arg1
那么为什么64位C会这样做呢?仅将所有内容压入堆栈而不是将前6个参数放在寄存器中只是将它们移到函数序言中的堆栈中是否容易得多?
答案 0 :(得分:5)
不是将前6个参数放在寄存器中只是为了将它们移到函数序言中的堆栈上?
我正在查看gcc生成的一些代码,而这正是它一直在做的事情。
然后您忘记启用优化。 gcc -O0
会将所有内容溢出到内存中,因此您可以在单步执行时使用调试器对其进行修改。显然,这对于性能而言是可怕的,因此除非您通过使用-O0
进行编译来强制编译器执行,否则编译器不会这样做。
x86-64系统V允许int add(int x, int y) { return x+y; }
编译为
lea eax, [rdi + rsi]
/ ret
,这就是编译器实际上所做的,正如您在the Godbolt compiler explorer上看到的那样。
Stack-args调用约定缓慢且过时。自从x86-64出现之前,RISC机器就一直使用register-args调用约定,并且在仍然关心32位x86的OS(即Windows)上,有更好的调用约定,例如__vectorcall
,它们传递了前2个整数寄存器中的参数。
i386系统V尚未被取代,因为人们大多不关心其他OS上的32位性能。我们只使用经过精心设计的x86-64 System V调用约定的64位代码。
有关调用约定设计中寄存器args与保留调用与保留调用寄存器之间的权衡的更多信息,请参见Why not store function parameters in float registers?,以及Why does Windows64 use a different calling convention from all other OSes on x86-64?。