Go 函數(shù)調(diào)用約定與棧

調(diào)用約定 (calling convention) 是調(diào)用方 (caller) 與被調(diào)用方 (callee) 對于函數(shù)調(diào)用的參數(shù)與返回值的傳遞方式糖权、傳遞順序的約定绽淘,只有雙方遵守相同的約定,才能保證參數(shù)的正確傳遞测萎,以及保證函數(shù)棧的平衡亡电。

Go 1.17 版本之前,采用基于棧的調(diào)用約定硅瞧,即函數(shù)的參數(shù)與返回值都通過棧來傳遞份乒,這種方式的優(yōu)點(diǎn)是實(shí)現(xiàn)簡單,無需考慮不同架構(gòu)下cpu寄存器的差異腕唧,更容易實(shí)現(xiàn)跨平臺(tái)或辖。然而由于內(nèi)存的訪問時(shí)延遠(yuǎn)高于寄存器,導(dǎo)致 Golang 的函數(shù)調(diào)用性能要低于 C/C++ 等使用寄存器傳遞參數(shù)的編程語言枣接。

Golang 在 1.17 版本中引入了基于寄存器的函數(shù)調(diào)用約定颂暇,本文將簡要介紹該調(diào)用約定函似,并通過具體的實(shí)驗(yàn)來驗(yàn)證并加深理解磁奖。

Go internal ABI specification

Go internal ABI specification 詳細(xì)介紹了 Go 1.17 之后的應(yīng)用程序二進(jìn)制接口 (Application Binary Interface, ABI)糜值。

首先是各種基礎(chǔ)類型的大小與內(nèi)存對齊約定满钟,例如 int 類型在 64 位架構(gòu)下的大小為 8 字節(jié)鸟缕,并且需要對齊到 8 字節(jié)瘫筐。棧上的變量如果不對齊矩桂,編譯器會(huì)插入 padding 使之對齊软族。

Type 64-bit 32-bit
Size Align Size Align
bool, uint8, int8 1 1 1 1
uint16, int16 2 2 2 2
uint32, int32 4 4 4 4
uint64, int64 8 8 8 4
int, uint 8 8 4 4
float32 4 4 4 4
float64 8 8 8 4
complex64 8 4 8 4
complex128 16 8 16 4
uintptr, *T, unsafe.Pointer 8 8 4 4

棧幀結(jié)構(gòu)如下:棧幀由高地址向低地址增長添谊,依次是上一個(gè)函數(shù)的程序計(jì)數(shù)器 (Program Counter, PC)财喳、上一個(gè)函數(shù)的基址寄存器 (Base Pointer, BP)、函數(shù)的局部變量、調(diào)用函數(shù)的返回值耳高、調(diào)用函數(shù)的參數(shù)扎瓶。

+------------------------------+
| return PC                    |
| RBP on entry                 |
| ... locals ...               |
| ... outgoing arguments ...   |
+------------------------------+ ↓ lower addresses

寄存器約定 (amd64):

  1. 用于整型參數(shù)和返回值的通用寄存器:RAX、RBX泌枪、RCX栗弟、RDI、RSI工闺、R8乍赫、R9、R10陆蟆、R11
  2. 用于浮點(diǎn)參數(shù)和返回值的浮點(diǎn)寄存器:X0~X14
  3. 其他是用于特殊目的的寄存器:
寄存器 名稱含義 值含義
RSP 棧頂指針 (Stack pointer) 固定
RBP 棧幀指針(基址)(Frame pointer) 固定
RDX 閉包上下文指針 臨時(shí)
R12 臨時(shí)
R13 臨時(shí)
R14 當(dāng)前 go routine 臨時(shí)
R15 GOT reference temporary 動(dòng)態(tài)鏈接時(shí)固定
X15 零值寄存器 固定為0

變量在棧上的排列順序:從低地址到高地址依次為通過棧傳遞的 receiver 參數(shù)雷厂、通過棧傳遞的若干個(gè)參數(shù)、通過棧傳遞的函數(shù)返回值叠殷、寄存器參數(shù)溢出空間改鲫。其中所有參數(shù)都會(huì)根據(jù)其類型處理內(nèi)存對齊,不對齊的情況下編譯器計(jì)算對齊所需的 padding 空間使之對齊林束。

+------------------------------+
|             . . .            |
| 2nd reg argument spill space |
| 1st reg argument spill space |
| <pointer-sized alignment>    |
|             . . .            |
| 2nd stack-assigned result    |
| 1st stack-assigned result    |
| <pointer-sized alignment>    |
|             . . .            |
| 2nd stack-assigned argument  |
| 1st stack-assigned argument  |
| stack-assigned receiver      |
+------------------------------+ ↓ lower addresses

函數(shù)的調(diào)用者負(fù)責(zé)保存寄存器像棘,被調(diào)用者無需保存寄存器,因此可以會(huì)覆蓋任何沒有固定含義的寄存器壶冒,包括參數(shù)寄存器缕题。

代碼示例

示例代碼如下,保存為 main.go胖腾。

package main

func main() {
    r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12 := test(71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83)
    ret := r0 + r1 + r2 + r3 + r4 + r5 + r6 + r7 + r8 + r9 + r10 + r11 + r12
    println(ret)
}

func test(a0 int, a1 int, a2 int, a3 int, a4 int, a5 int, a6 int, a7 int, a8 int, a9 int, a10 int, a11 int, a12 int) (int, int, int, int, int, int, int, int, int, int, int, int, int) {
    var arr [13]int
    arr[0] = a0 + 31
    arr[1] = a1 + 32
    arr[2] = a1 + 33
    arr[3] = a1 + 34
    arr[4] = a1 + 35
    arr[5] = a1 + 36
    arr[6] = a0 + 37
    arr[7] = a1 + 38
    arr[8] = a1 + 39
    arr[9] = a1 + 40
    arr[10] = a1 + 41
    arr[11] = a1 + 42
    arr[12] = a1 + 43

    arr[2] = arr[0] + arr[1]
    arr[4] = arr[2] + arr[3]
    arr[6] = arr[4] + arr[5]
    arr[8] = arr[6] + arr[7]
    arr[10] = arr[8] + arr[9]
    arr[12] = arr[10] + arr[11]

    arr[1] = arr[1] * 3
    arr[1] = arr[1] + 11

    return arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8], arr[9], arr[10], arr[11], arr[12]
}

編譯并輸出到 main.s 文件中烟零。go 版本為 go1.18.7 linux/amd64

go tool compile -S main.go > main.s

在 go 代碼中咸作,main 函數(shù)首先調(diào)用 test 函數(shù)锨阿,傳遞了 13 個(gè) int 參數(shù),分別為 71~83 之間依次遞增的數(shù)字记罚。
在匯編代碼中墅诡,main 函數(shù)通過 SUBQ $152, SP 開辟了 152 字節(jié)的棧空間桐智,然后將函數(shù)調(diào)用方的 BP 保存到棧底即 144(SP) 處末早,接著將 144(SP) 處設(shè)置為新的 BPFUNCDATA 指令和 PCDATA 指令與垃圾回收有關(guān)酵使,這里不討論荐吉。
main 函數(shù)的主要邏輯中焙糟,首先將 80 移動(dòng)到棧頂口渔,然后將 81 移動(dòng)到棧頂?shù)南乱粋€(gè) slot,將 82 和 83 移動(dòng)到接下來的 slot 中穿撮,接著依次將 71~79 移動(dòng)到 AX~R11 寄存器中缺脉,然后調(diào)用 test 函數(shù)痪欲。在 test 函數(shù)返回之后,從 AX~R11攻礼、32(SP)~56(SP) 中取出返回值业踢,累加到 DX 寄存器上,隨機(jī)將 DX 中的值寫入 136(SP) 也就是 ret 變量礁扮,后面的一段代碼是內(nèi)聯(lián)的 println 代碼知举,printlockprintunlock 表示加鎖與解鎖,加鎖后將 136(SP) 寫入 AX 作為參數(shù)太伊,調(diào)用 runtime.printnl 函數(shù)輸出雇锡,忽略返回值。最后將 144(SP) 即調(diào)用方 BP 寫回 BP僚焦,通過 ADDQ $152, SP 釋放 main 函數(shù)的椕烫幔空間,然后執(zhí)行 RET 指令恢復(fù) PC

"".main STEXT size=235 args=0x0 locals=0x98 funcid=0x0 align=0x0
    0x0000 00000 (main.go:3)    TEXT    "".main(SB), ABIInternal, $152-0
    0x0000 00000 (main.go:3)    LEAQ    -24(SP), R12
    0x0005 00005 (main.go:3)    CMPQ    R12, 16(R14)
    0x0009 00009 (main.go:3)    PCDATA  $0, $-2
    0x0009 00009 (main.go:3)    JLS 225
    0x000f 00015 (main.go:3)    PCDATA  $0, $-1
    0x000f 00015 (main.go:3)    SUBQ    $152, SP
    0x0016 00022 (main.go:3)    MOVQ    BP, 144(SP)
    0x001e 00030 (main.go:3)    LEAQ    144(SP), BP
    0x0026 00038 (main.go:3)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0026 00038 (main.go:3)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0026 00038 (main.go:4)    MOVQ    $80, (SP)
    0x002e 00046 (main.go:4)    MOVQ    $81, 8(SP)
    0x0037 00055 (main.go:4)    MOVQ    $82, 16(SP)
    0x0040 00064 (main.go:4)    MOVQ    $83, 24(SP)
    0x0049 00073 (main.go:4)    MOVL    $71, AX
    0x004e 00078 (main.go:4)    MOVL    $72, BX
    0x0053 00083 (main.go:4)    MOVL    $73, CX
    0x0058 00088 (main.go:4)    MOVL    $74, DI
    0x005d 00093 (main.go:4)    MOVL    $75, SI
    0x0062 00098 (main.go:4)    MOVL    $76, R8
    0x0068 00104 (main.go:4)    MOVL    $77, R9
    0x006e 00110 (main.go:4)    MOVL    $78, R10
    0x0074 00116 (main.go:4)    MOVL    $79, R11
    0x007a 00122 (main.go:4)    PCDATA  $1, $0
    0x007a 00122 (main.go:4)    CALL    "".test(SB)
    0x007f 00127 (main.go:5)    LEAQ    (BX)(AX*1), DX
    0x0083 00131 (main.go:5)    ADDQ    CX, DX
    0x0086 00134 (main.go:5)    ADDQ    DI, DX
    0x0089 00137 (main.go:5)    ADDQ    SI, DX
    0x008c 00140 (main.go:5)    ADDQ    R8, DX
    0x008f 00143 (main.go:5)    ADDQ    R9, DX
    0x0092 00146 (main.go:5)    ADDQ    R10, DX
    0x0095 00149 (main.go:5)    ADDQ    R11, DX
    0x0098 00152 (main.go:5)    ADDQ    32(SP), DX
    0x009d 00157 (main.go:5)    ADDQ    40(SP), DX
    0x00a2 00162 (main.go:5)    ADDQ    48(SP), DX
    0x00a7 00167 (main.go:5)    ADDQ    56(SP), DX
    0x00ac 00172 (main.go:5)    MOVQ    DX, "".ret+136(SP)
    0x00b4 00180 (main.go:6)    CALL    runtime.printlock(SB)
    0x00b9 00185 (main.go:6)    MOVQ    "".ret+136(SP), AX
    0x00c1 00193 (main.go:6)    CALL    runtime.printint(SB)
    0x00c6 00198 (main.go:6)    CALL    runtime.printnl(SB)
    0x00cb 00203 (main.go:6)    CALL    runtime.printunlock(SB)
    0x00d0 00208 (main.go:7)    MOVQ    144(SP), BP
    0x00d8 00216 (main.go:7)    ADDQ    $152, SP
    0x00df 00223 (main.go:7)    NOP
    0x00e0 00224 (main.go:7)    RET
    0x00e1 00225 (main.go:7)    NOP
    0x00e1 00225 (main.go:3)    PCDATA  $1, $-1
    0x00e1 00225 (main.go:3)    PCDATA  $0, $-2
    0x00e1 00225 (main.go:3)    CALL    runtime.morestack_noctxt(SB)
    0x00e6 00230 (main.go:3)    PCDATA  $0, $-1
    0x00e6 00230 (main.go:3)    JMP 0

test 函數(shù)中申請了一個(gè)長度為 13 的數(shù)組芳悲,首先將參數(shù)依次加上一個(gè)不同的值立肘,復(fù)制給數(shù)組中對應(yīng)順序的元素。

test 函數(shù)的匯編代碼如下名扛,首先通過 SUBQ $112, SP 開辟了 112 字節(jié)的椓履辏空間,然后將調(diào)用方也就是 main 函數(shù)的 BP 放入棧底 104(SP) 的位置肮韧,然后將 104(SP) 的地址設(shè)置為新的 BP踢故。

接著開始計(jì)算 ,LEAQ 31(AX), R12 可以理解為 R12 = AX + 31惹苗,隨后 MOVQ R12, "".arr(SP)R12 保存到 (SP) 處殿较,即 arr[0],故這兩條匯編指令實(shí)現(xiàn)了 arr[0] = a0 + 31桩蓉。接下來的匯編指令以此類推淋纲,使用 R12DX 作為數(shù)據(jù)寄存器,存放中間結(jié)果院究,接著將結(jié)果保存到棧中洽瞬。有一個(gè)例外是 DI,由于數(shù)據(jù)清零指令 DUFFZERO 會(huì)修改 DI业汰,因此先將 DI 保存到 DX伙窃,并用 DX 完成對應(yīng)的計(jì)算。從 arr[9] = a9 + 40 開始样漆,變成先 MOVQ "".a9+120(SP), DX 從棧中取出參數(shù)存到 DX为障,然后 ADDQ $40, DXDX 做加法,最后 MOVQ DX, "".arr+72(SP) 將結(jié)果寫入數(shù)組。

接下來是將數(shù)組中的兩個(gè)元素相加并賦值給另一個(gè)元素鳍怨,例如 arr[2] = arr[0] + arr[1]呻右。匯編代碼中,首先 MOVQ "".arr(SP), DXarr[0] 存入 DX鞋喇,然后 ADDQ "".arr+8(SP), DX8(SP) 添加到 DX 上声滥,最后 MOVQ DX, "".arr+16(SP)DX 寫入 16(SP)

對于 arr[1] = arr[1] * 3侦香,首先 MOVQ "".arr+8(SP), R13arr[1] 存入 R13落塑,接著 LEAQ (R13)(R13*2), R15 相當(dāng)于 R15=R13+R13*2,從而實(shí)現(xiàn) 3 倍的乘法罐韩,然后 MOVQ R15, "".arr+8(SP)R15 寫回棧中芜赌。

計(jì)算完成后依次將 a[0] ~ a[12] 寫入 AX`R15`、`152(SP)`176(SP)伴逸。最后 MOVQ 104(SP), BP 恢復(fù) BP缠沈,ADDQ $112, SP 恢復(fù) SPRET 恢復(fù) PC错蝴。

注1:RET 指令與 CALL 指令配對使用洲愤,每一個(gè)函數(shù)都應(yīng)該以 RET 結(jié)尾。CALL 指令首先將 CALL 指令的下一條指令入棧(等價(jià)于PUSH顷锰,會(huì)修改 SP)柬赐,然后修改 PC 跳轉(zhuǎn)到被調(diào)函數(shù)處執(zhí)行,被調(diào)函數(shù)返回時(shí)調(diào)用 RET 指令官紫,從棧中彈出 PC (等價(jià)于 POP肛宋,會(huì)修改SP)從而執(zhí)行 CALL 指令的下一條指令。

注2:MOVUPS X15, "".arr(SP) 指令表示 arr 位于棧頂束世,X15 是零值寄存器酝陈,MOVUPSX15 中取出 0,賦值到 arr 的前 16 個(gè)字節(jié)中毁涉。DUFFZERO 用于清空內(nèi)存地址沉帮,從而實(shí)現(xiàn)棧上變量的 0 值初始化。參數(shù)沒看懂贫堰。

"".test STEXT nosplit size=395 args=0x88 locals=0x70 funcid=0x0 align=0x0
    0x0000 00000 (main.go:9)    TEXT    "".test(SB), NOSPLIT|ABIInternal, $112-136
    0x0000 00000 (main.go:9)    SUBQ    $112, SP
    0x0004 00004 (main.go:9)    MOVQ    BP, 104(SP)
    0x0009 00009 (main.go:9)    LEAQ    104(SP), BP
    0x000e 00014 (main.go:9)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x000e 00014 (main.go:9)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x000e 00014 (main.go:9)    FUNCDATA    $5, "".test.arginfo1(SB)
    0x000e 00014 (main.go:9)    FUNCDATA    $6, "".test.argliveinfo(SB)
    0x000e 00014 (main.go:9)    PCDATA  $3, $1
    0x000e 00014 (main.go:10)   MOVUPS  X15, "".arr(SP)
    0x0013 00019 (main.go:9)    MOVQ    DI, DX
    0x0016 00022 (main.go:10)   LEAQ    "".arr+8(SP), DI
    0x001b 00027 (main.go:10)   PCDATA  $0, $-2
    0x001b 00027 (main.go:10)   LEAQ    -32(DI), DI
    0x001f 00031 (main.go:10)   NOP
    0x0020 00032 (main.go:10)   DUFFZERO    $331
    0x0033 00051 (main.go:11)   PCDATA  $0, $-1
    0x0033 00051 (main.go:11)   LEAQ    31(AX), R12
    0x0037 00055 (main.go:11)   MOVQ    R12, "".arr(SP)
    0x003b 00059 (main.go:12)   LEAQ    32(BX), R12
    0x003f 00063 (main.go:12)   MOVQ    R12, "".arr+8(SP)
    0x0044 00068 (main.go:13)   LEAQ    33(CX), R12
    0x0048 00072 (main.go:13)   MOVQ    R12, "".arr+16(SP)
    0x004d 00077 (main.go:14)   ADDQ    $34, DX
    0x0051 00081 (main.go:14)   MOVQ    DX, "".arr+24(SP)
    0x0056 00086 (main.go:15)   LEAQ    35(SI), DX
    0x005a 00090 (main.go:15)   MOVQ    DX, "".arr+32(SP)
    0x005f 00095 (main.go:16)   LEAQ    36(R8), DX
    0x0063 00099 (main.go:16)   MOVQ    DX, "".arr+40(SP)
    0x0068 00104 (main.go:17)   LEAQ    37(R9), DX
    0x006c 00108 (main.go:17)   MOVQ    DX, "".arr+48(SP)
    0x0071 00113 (main.go:18)   LEAQ    38(R10), DX
    0x0075 00117 (main.go:18)   MOVQ    DX, "".arr+56(SP)
    0x007a 00122 (main.go:19)   LEAQ    39(R11), DX
    0x007e 00126 (main.go:19)   MOVQ    DX, "".arr+64(SP)
    0x0083 00131 (main.go:20)   MOVQ    "".a9+120(SP), DX
    0x0088 00136 (main.go:20)   ADDQ    $40, DX
    0x008c 00140 (main.go:20)   MOVQ    DX, "".arr+72(SP)
    0x0091 00145 (main.go:21)   MOVQ    "".a10+128(SP), DX
    0x0099 00153 (main.go:21)   ADDQ    $41, DX
    0x009d 00157 (main.go:21)   MOVQ    DX, "".arr+80(SP)
    0x00a2 00162 (main.go:22)   MOVQ    "".a11+136(SP), DX
    0x00aa 00170 (main.go:22)   ADDQ    $42, DX
    0x00ae 00174 (main.go:22)   MOVQ    DX, "".arr+88(SP)
    0x00b3 00179 (main.go:23)   MOVQ    "".a12+144(SP), DX
    0x00bb 00187 (main.go:23)   ADDQ    $43, DX
    0x00bf 00191 (main.go:23)   MOVQ    DX, "".arr+96(SP)
    0x00c4 00196 (main.go:25)   MOVQ    "".arr(SP), DX
    0x00c8 00200 (main.go:25)   ADDQ    "".arr+8(SP), DX
    0x00cd 00205 (main.go:25)   MOVQ    DX, "".arr+16(SP)
    0x00d2 00210 (main.go:26)   MOVQ    "".arr+24(SP), R12
    0x00d7 00215 (main.go:26)   ADDQ    R12, DX
    0x00da 00218 (main.go:26)   MOVQ    DX, "".arr+32(SP)
    0x00df 00223 (main.go:27)   MOVQ    "".arr+40(SP), R12
    0x00e4 00228 (main.go:27)   ADDQ    R12, DX
    0x00e7 00231 (main.go:27)   MOVQ    DX, "".arr+48(SP)
    0x00ec 00236 (main.go:28)   MOVQ    "".arr+56(SP), R12
    0x00f1 00241 (main.go:28)   ADDQ    R12, DX
    0x00f4 00244 (main.go:28)   MOVQ    DX, "".arr+64(SP)
    0x00f9 00249 (main.go:29)   MOVQ    "".arr+72(SP), R12
    0x00fe 00254 (main.go:29)   ADDQ    R12, DX
    0x0101 00257 (main.go:29)   MOVQ    DX, "".arr+80(SP)
    0x0106 00262 (main.go:30)   MOVQ    "".arr+88(SP), R12
    0x010b 00267 (main.go:30)   ADDQ    DX, R12
    0x010e 00270 (main.go:30)   MOVQ    R12, "".arr+96(SP)
    0x0113 00275 (main.go:32)   MOVQ    "".arr+8(SP), R13
    0x0118 00280 (main.go:32)   LEAQ    (R13)(R13*2), R15
    0x011d 00285 (main.go:32)   MOVQ    R15, "".arr+8(SP)
    0x0122 00290 (main.go:33)   LEAQ    (R13)(R13*2), BX
    0x0127 00295 (main.go:33)   LEAQ    11(BX), BX
    0x012b 00299 (main.go:33)   MOVQ    BX, "".arr+8(SP)
    0x0130 00304 (main.go:35)   MOVQ    "".arr(SP), AX
    0x0134 00308 (main.go:35)   MOVQ    "".arr+16(SP), CX
    0x0139 00313 (main.go:35)   MOVQ    "".arr+24(SP), DI
    0x013e 00318 (main.go:35)   MOVQ    "".arr+32(SP), SI
    0x0143 00323 (main.go:35)   MOVQ    "".arr+40(SP), R8
    0x0148 00328 (main.go:35)   MOVQ    "".arr+48(SP), R9
    0x014d 00333 (main.go:35)   MOVQ    "".arr+56(SP), R10
    0x0152 00338 (main.go:35)   MOVQ    "".arr+64(SP), R11
    0x0157 00343 (main.go:35)   MOVQ    "".arr+72(SP), R13
    0x015c 00348 (main.go:35)   MOVQ    "".arr+88(SP), R15
    0x0161 00353 (main.go:35)   MOVQ    R13, "".~r9+152(SP)
    0x0169 00361 (main.go:35)   MOVQ    DX, "".~r10+160(SP)
    0x0171 00369 (main.go:35)   MOVQ    R15, "".~r11+168(SP)
    0x0179 00377 (main.go:35)   MOVQ    R12, "".~r12+176(SP)
    0x0181 00385 (main.go:35)   MOVQ    104(SP), BP
    0x0186 00390 (main.go:35)   ADDQ    $112, SP
    0x018a 00394 (main.go:35)   RET

棧幀圖示

上述代碼中兩個(gè)函數(shù)對應(yīng)的棧幀可以由下圖表示穆壕。

左邊為 main 函數(shù)的棧幀,從高地址到低地址依次為:

  1. main 函數(shù)的調(diào)用者的 PC:這是由 CALL 指令自動(dòng)壓入棧的其屏,并因此導(dǎo)致調(diào)用者的 SP 增加了 8 字節(jié)喇勋。
  2. main 函數(shù)的調(diào)用者的 BP:main 函數(shù)在申請棧空間后偎行,將上一個(gè)函數(shù)的 BP 放到棧底川背,并且調(diào)整 BP 指向該位置贰拿。
  3. main 函數(shù)的局部變量 ret:占 8 個(gè)字節(jié)的 int 類型。
  4. 寄存器參數(shù)的溢出區(qū)域:如果被調(diào)函數(shù)中參數(shù)非常多渗常,寄存器不夠用了,寄存器參數(shù)就存到這些位置汗盘。
  5. 棧上返回值:存放在棧上的 test 函數(shù)的返回值皱碘。
  6. 棧上入?yún)ⅲ捍娣旁跅I系?test 函數(shù)的入?yún)ⅰ?/li>
stask.png

右邊為 test 函數(shù)的棧幀,從高地址到低地址依次為:

  1. main 函數(shù)的 PC:這是由 CALL 指令自動(dòng)壓入棧的隐孽,并因此導(dǎo)致 SP 增加了 8 字節(jié)癌椿。
  2. main 函數(shù)的 BP:test 函數(shù)在申請棧空間后菱阵,將 main 函數(shù)的 BP 放到棧底踢俄,并且調(diào)整 BP 指向該位置。
  3. test 函數(shù)的局部變量 arr:一共 13 個(gè) int 類型的數(shù)組晴及。

由于沒有調(diào)用其他函數(shù)都办,因此 test 函數(shù)的棧幀中沒有參數(shù)、返回值虑稼、寄存器溢出區(qū)琳钉。

值得注意的是,這兩個(gè)函數(shù)的棧幀在內(nèi)存中是連續(xù)的蛛倦,即途中虛線連接的部分歌懒,在內(nèi)存中是完全相同的區(qū)域,只是由于兩個(gè)函數(shù)的 SP 值不同溯壶,相對的偏移量也不同及皂。

函數(shù)的參數(shù)與返回值存放在調(diào)用方的棧幀中,棧上變量的訪問通常不是使用 PUSH/POP 而是使用 SP 加上偏移量訪問且改,類似于數(shù)組验烧。

棧的平衡由被調(diào)方負(fù)責(zé),編譯器計(jì)算函數(shù)所需的椨瞩耍空間噪窘,函數(shù)首先將 SP 減去所需棧空間用于調(diào)整棧頂效扫,實(shí)現(xiàn)“批量入椌蠹啵”的效果,隨后將調(diào)用方的 BP 保存到棧底菌仁。函數(shù)返回之前先從棧中恢復(fù) BP浩习,然后將 SP 加上最初減去的值,實(shí)現(xiàn)“批量出椉们穑”的效果谱秽。

函數(shù)的調(diào)用與返回過程中的跳轉(zhuǎn)由 CALL/RET 指令負(fù)責(zé)洽蛀。CALL 指令首先將 CALL 指令的下一條指令的地址通過 PUSH 入棧,然后 JUMP 到被調(diào)函數(shù)的地址(修改 PC)運(yùn)行疟赊。被調(diào)函數(shù)返回之前最后一條指令是 RET郊供,它將棧中存放的指令地址 POPPC 寄存器中,跳轉(zhuǎn)到 CALL 指令的下一條指令開始執(zhí)行近哟。

小結(jié)

Golang 函數(shù)調(diào)用過程中同時(shí)使用寄存器與棧進(jìn)行參數(shù)傳遞驮审,優(yōu)先使用寄存器,寄存器不夠用了才使用棧吉执。函數(shù)的第 1~9 個(gè)參數(shù)依次使用 AX疯淫、BX、CX戳玫、DI熙掺、SI、R8咕宿、R9币绩、R10、R11 寄存器府阀。從第 10 個(gè)參數(shù)開始类浪,使用棧傳遞,并且是從棧頂向棧低依次排列肌似。執(zhí)行順序方面费就,入棧操作先執(zhí)行,然后再對寄存器賦值川队。由于寄存器溢出區(qū)域的存在力细,寄存器傳參并不會(huì)減小棧內(nèi)存占用。

使用寄存器傳遞參數(shù)指令數(shù)量比使用棧傳遞參數(shù)更少固额。在上面的例子中眠蚂,使用寄存器傳遞的參數(shù)在計(jì)算時(shí)可以直接計(jì)算,然后存到棧上斗躏,而使用棧傳遞的參數(shù)則需要先從棧中加載到寄存器上逝慧,然后計(jì)算,最后存到棧上啄糙。更重要的是笛臣,寄存器的讀寫速度遠(yuǎn)高于 CPU Cache 與內(nèi)存,而棧則是存放在內(nèi)存中隧饼。一組具有代表性的 Go 包和程序的基準(zhǔn)顯示性能提高了約 5%沈堡,二進(jìn)制文件大小通常減少了約 2%。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末燕雁,一起剝皮案震驚了整個(gè)濱河市诞丽,隨后出現(xiàn)的幾起案子鲸拥,更是在濱河造成了極大的恐慌,老刑警劉巖僧免,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刑赶,死亡現(xiàn)場離奇詭異,居然都是意外死亡懂衩,警方通過查閱死者的電腦和手機(jī)撞叨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勃痴,“玉大人谒所,你說我怎么就攤上這事热康∨嫔辏” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵姐军,是天一觀的道長铁材。 經(jīng)常有香客問我,道長奕锌,這世上最難降的妖魔是什么著觉? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮惊暴,結(jié)果婚禮上饼丘,老公的妹妹穿的比我還像新娘。我一直安慰自己辽话,他們只是感情好肄鸽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著油啤,像睡著了一般典徘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上益咬,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天逮诲,我揣著相機(jī)與錄音,去河邊找鬼幽告。 笑死梅鹦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的冗锁。 我是一名探鬼主播帘瞭,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蒿讥!你這毒婦竟也來了蝶念?” 一聲冷哼從身側(cè)響起抛腕,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媒殉,沒想到半個(gè)月后担敌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廷蓉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年全封,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桃犬。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刹悴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出攒暇,到底是詐尸還是另有隱情土匀,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布形用,位于F島的核電站就轧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏田度。R本人自食惡果不足惜妒御,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镇饺。 院中可真熱鬧乎莉,春花似錦、人聲如沸奸笤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揭保。三九已至肥橙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秸侣,已是汗流浹背存筏。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留味榛,地道東北人椭坚。 一個(gè)月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像搏色,于是被迫代替她去往敵國和親善茎。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容