上一篇 博文提到了 X64 下 MSVC 如何傳遞參數,但是沒有涉及到當參數個數大於 4 的時候如何分配內存空間的問題,接下來我們來探究這個問題。
RSP 和 RBP
按照上面提到的博文,我們進行如下實驗:
- 所有參數都是
struct Arg
, 並且sizeof(Arg) == 16
。 - 實驗中,參數的個數
k = 6, 7, 8
,先觀察彙編後rsp
和rbp
寄存器的變化。
實驗:
1. k = 6
; 28 : void Call() {
$LN3:
00000 40 55 push rbp
00002 56 push rsi
00003 57 push rdi
00004 48 81 ec f0 03
00 00 sub rsp, 1008 ; 000003f0H
0000b 48 8d 6c 24 30 lea rbp, QWORD PTR [rsp+48]
00010 48 8b fc mov rdi, rsp
- k = 7
; 28 : void Call() {
$LN3:
00000 40 55 push rbp
00002 56 push rsi
00003 57 push rdi
00004 48 81 ec 60 04
00 00 sub rsp, 1120 ; 00000460H
0000b 48 8d 6c 24 40 lea rbp, QWORD PTR [rsp+64]
00010 48 8b fc mov rdi, rsp
- k = 8
; 28 : void CallFuck() {
$LN3:
00000 40 55 push rbp
00002 56 push rsi
00003 57 push rdi
00004 48 81 ec c0 04
00 00 sub rsp, 1216 ; 000004c0H
0000b 48 8d 6c 24 40 lea rbp, QWORD PTR [rsp+64]
這裏我們需要明確一點,就是 rbp
的意義和 rsp
的意義是什麼。
rbp
是屬於當前函數的棧空間基地址,rsp
是包含當前函數爲被調用函數準備的棧空間的基地址。
這點可以在彙編代碼中看出來。
我們可以看出,當 k=6 的時候,MSVC 利用 lea rbp, QWORD PTR [rsp+48]
使得 rbp == rsp + 48
k = 7, 8 時 rbp == rsp + 64
根據 X64 下的傳參規則,當 sizeof(struct)
不爲 8, 16, 32, 64 bits 時,將指向參數本體的指針放在對應的位置,因此,一個參數(這裏指這個指針,64 bits = 8 bytes)在棧上所佔用的內存應該爲 M = 8 * (#arg - 4)
。
所以, k = 6, M = 16; k = 7, M = 24; k = 8, M = 32; 再加上分配的 32 bytes 影子空間,那麼應該是
k = 6, rbp == rsp + 48
k = 7, rbp == rsp + 56
k = 8, rbp == rsp + 64·
實際情況呢,k = 7 我們和編譯器結果不一樣,實際情況是 rbp == rsp + 64
,原因在於
爲這些聚合類型作爲指針的傳遞 (包括__m128),調用方分配的臨時內存將是 16 字節對齊。
因此這些指針雖然單個大小是 8,所以在奇數個的時候爲了對齊要額外增加 8 !
Calling Convention
根據這個簡單的調用約定,我們可以畫出64位下函數調用時到底是個怎麼樣的內存結構。(時機應該是 call
指令執行完畢)
這張圖片認真理解,就解決了所有調用時對於內存空間如何分配的疑問。
See also:
本篇基於實驗得出,除了某些叫法不同,和微軟的官方文檔是一致的。