64.5 x86-64
64.5.1 Windows x64
在Win64里面传递函数参数的方法类似fastcall调用约定。前四个参数通过RCX,RDX,R8和R9寄存器传参,其余参数通过栈进行传递。调用者还必须预留32个字节或者4个64位的空间,让被调用者可以保存前四个参数。短函数可能直接使用通过寄存器传过来的值,但更大的可能是保存那些值后在进一步使用。
调用者还必须负责还原栈指针。
这个调用约定也用于Windows x86-64位系统上的DLL(而不是Win32的stdcall)。
例子
#include <stdio.h>
void f1(int a, int b, int c, int d, int e, int f, int g)
{
printf ("%d %d %d %d %d %d %d\n", a, b, c, d, e, f, g);
};
int main()
{
f1(1,2,3,4,5,6,7);
};
Listing 64.6: MSVC 2012 /0b
$SG2937 DB '%d %d %d %d %d %d %d', 0aH, 00H
main PROC
sub rsp, 72 ; 00000048H
mov DWORD PTR [rsp+48], 7
mov DWORD PTR [rsp+40], 6
mov DWORD PTR [rsp+32], 5
mov r9d, 4
mov r8d, 3
mov edx, 2
mov ecx, 1
call f1
xor eax, eax
add rsp, 72 ; 00000048H
ret 0
main ENDP
a$ = 80
b$ = 88
c$ = 96
d$ = 104
e$ = 112
f$ = 120
g$ = 128
f1 PROC
$LN3:
mov DWORD PTR [rsp+32], r9d
mov DWORD PTR [rsp+24], r8d
mov DWORD PTR [rsp+16], edx
mov DWORD PTR [rsp+8], ecx
sub rsp, 72 ; 00000048H
mov eax, DWORD PTR g$[rsp]
mov DWORD PTR [rsp+56], eax
mov eax, DWORD PTR f$[rsp]
mov DWORD PTR [rsp+48], eax
mov eax, DWORD PTR e$[rsp]
mov DWORD PTR [rsp+40], eax
mov eax, DWORD PTR d$[rsp]
mov DWORD PTR [rsp+32], eax
mov r9d, DWORD PTR c$[rsp]
mov r8d, DWORD PTR b$[rsp]
mov edx, DWORD PTR a$[rsp]
lea rcx, OFFSET FLAT:$SG2937
call printf
add rsp, 72 ; 00000048H
ret 0
f1 ENDP
在这里我们可以清楚看到这7个参数是如何传递的:4个参数通过寄存器传递而其余3个通过栈传递。f1()的反汇编代码一开始就把参数保存到“预留”的栈空间之中,这样做的目的是编译器并不能保证有足够的寄存器可以使用,如果不这样做的话这四个寄存器将被参数占用到函数执行结束。最后,预留栈空间是调用者的职责。
Listing 64.7: Optimizing MSVC 2012 /0b
$SG2777 DB '%d %d %d %d %d %d %d', 0aH, 00H
a$ = 80
b$ = 88
c$ = 96
d$ = 104
e$ = 112
f$ = 120
g$ = 128
f1 PROC
$LN3:
sub rsp, 72 ; 00000048H
mov eax, DWORD PTR g$[rsp]
mov DWORD PTR [rsp+56], eax
mov eax, DWORD PTR f$[rsp]
mov DWORD PTR [rsp+48], eax
mov eax, DWORD PTR e$[rsp]
mov DWORD PTR [rsp+40], eax
mov DWORD PTR [rsp+32], r9d
mov r9d, r8d
mov r8d, edx
mov edx, ecx
lea rcx, OFFSET FLAT:$SG2777
call printf
add rsp, 72 ; 00000048H
ret 0
f1 ENDP
main PROC
sub rsp, 72 ; 00000048H
mov edx, 2
mov DWORD PTR [rsp+48], 7
mov DWORD PTR [rsp+40], 6
lea r9d, QWORD PTR [rdx+2]
lea r8d, QWORD PTR [rdx+1]
lea ecx, QWORD PTR [rdx-1]
mov DWORD PTR [rsp+32], 5
call f1
xor eax, eax
add rsp, 72 ; 00000048H
ret 0
main ENDP
如果我们使用了编译优化的开关去编译上面的例子,它的反汇编码几乎是相同的,但是预留的栈空间将不被使用,因为在这里并不需要使用到预留的栈空间。
而且可以看到MSVC 2012是如何利用LEA指令来优化代码(A.6.2)。
我也不确定是否值得这么做。
更多的例子请看(74.1)
this指针的传递(C/C++)
this指针通过RCX传递,成员函数的第一个参数通过RDX传递,更多例子请看(51.1.1)。
64.5.2 Linux x64
Linux x86-64传递参数的方式几乎和Windows一样。但是是通过6个寄存器代替4个寄存器来传参(RDI,RSI,RDX,RCX,R8,R9),另外并没有预留的栈空间这回事。虽然,如果它需要/想要的话,可以把寄存器的值保存到栈之中。
Listing 64.8: Optimizing GCC 4.7.3
.LC0:
.string "%d %d %d %d %d %d %d\n"
f1:
sub rsp, 40
mov eax, DWORD PTR [rsp+48]
mov DWORD PTR [rsp+8], r9d
mov r9d, ecx
mov DWORD PTR [rsp], r8d
mov ecx, esi
mov r8d, edx
mov esi, OFFSET FLAT:.LC0
mov edx, edi
mov edi, 1
mov DWORD PTR [rsp+16], eax
xor eax, eax
call __printf_chk
add rsp, 40
ret
main:
sub rsp, 24
mov r9d, 6
mov r8d, 5
mov DWORD PTR [rsp], 7
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
call f1
add rsp, 24
ret
注意:这里的值是写入到32-bit的寄存器(EAX…)而不是整个64-bit寄存器(RAX…)。这是因为写入到32-bit寄存器的时候会自动清空高32-bit。据说,这是为了方便把代码移植到x86-64。