• CPU在执行程序时,IP寄存器会指向下一条即将被执行的指令,而SP寄存器会指向栈顶。
  • CALL指令会先把下一条指令的地址压入栈中,这就是所谓的返回地址。然后跳转到被调用函数去执行。当被调用函数执行完成后会返回到CALL指令压栈的返回地址继续执行。由于CALL指令引发了入栈和指令跳转,所以SP和IP寄存器的值都发生了改变。
  • RET指令会从栈上弹出返回地址,然后跳转到该地址处继续执行。

栈帧布局

  1. 编译器生成的指令负责栈帧的而分配与释放。栈帧的布局也是由编译器在编译阶段确定的,其依据就是函数代码,所以也可以说函数栈帧是由编译器管理的。

  2. 参照函数栈帧布局图,函数栈帧包含以下几部分:

    • return address:函数返回地址,占用一个指针大小空间。实际上是在函数被调用时由CALL指令自动压栈的,并非由被调用函数分配。
    • caller's BP:调用者的栈帧基址,占用一个指针大小空间。用来将调用路径上所有的栈帧连成一个链表,方便栈回溯,只在部分平台架构上存在。函数通过将栈指针SP直接向下移动指定大小,一次性分配caller's BP、locals和args to callee所占用的空间,在x86架构上就是使用SUB指令将SP减去指定大小的。
    • locals:局部变量区间,占用若干机器字节。用来存放函数的局部变量,根据函数的局部变量占用空间大小来分配,没有局部变量的函数不分配。
    • args to callee:调用传参区域,占用若干机器字节。这一区域所占空间大小,会按照当前函数调用的所有函数中返回值加上参数所占用的空间来分配。当没有调用任何函数时,不需要分配该区间。

  1. 综上,只有 return address 是一定存在的,其他三个区间都要根据实际情况分析。
  2. 按照一般代码的逻辑,函数的栈帧应该包含返回值、参数、返回地址和局部变量这四部分。

传参

  1. 交换两个变量的值(值传递)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

func main() {
    a,b := 1,2
    swap(a,b)
}

//go:noinline
func swap(a,b int) {
    a,b = b,a
}

相关汇编代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
  0x4551e0        493b6610            CMPQ 0x10(R14), SP      # 比较栈是否溢出
  0x4551e4        7639                JBE 0x45521f
  0x4551e6        4883ec28            SUBQ $0x28, SP          # main.main函数预分配栈帧大小
  0x4551ea        48896c2420          MOVQ BP, 0x20(SP)       # 存储被调用函数runtime.main的BP
  0x4551ef        488d6c2420          LEAQ 0x20(SP), BP       # 调整当前main.main的BP
    a,b := 1,2
  0x4551f4        48c744241801000000  MOVQ $0x1, 0x18(SP)     # 变量a
  0x4551fd        48c744241002000000  MOVQ $0x2, 0x10(SP)     # 变量b
    swap(a,b)
  0x455206        488b442418          MOVQ 0x18(SP), AX       # main.swap的a参数 AX = a = 1
  0x45520b        bb02000000          MOVL $0x2, BX           # main.swap的b参数 BX = 2
  0x455210        e82b000000          CALL main.swap(SB)      # 调用swap函数
}
  0x455215        488b6c2420          MOVQ 0x20(SP), BP       # 恢复runtime.main的BP
  0x45521a        4883c428            ADDQ $0x28, SP          # 调整栈
  0x45521e        c3                  RET                     # 函数返回 ,弹出runtime.main的下一条IP地址,SP减8
func main() {
  0x45521f        90                  NOPL
  0x455220        e83bcdffff          CALL runtime.morestack_noctxt.abi0(SB)  # 栈溢出时会跳转到这里来,从新分配栈空间
  0x455225        ebb9                JMP main.main(SB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func swap(a,b int) {
  0x455240        4883ec10        SUBQ $0x10, SP          # 预分配栈大小
  0x455244        48896c2408      MOVQ BP, 0x8(SP)        # 存储main.main的BP
  0x455249        488d6c2408      LEAQ 0x8(SP), BP        # 调整BP寄存器
  0x45524e        4889442418      MOVQ AX, 0x18(SP)       # 把参数a放入栈上,在main.main的栈上
  0x455253        48895c2420      MOVQ BX, 0x20(SP)       # 把参数b放入栈上,在main.main的栈上
    a,b = b,a
  0x455258        48890424        MOVQ AX, 0(SP)          # 把参数a得值临时放入(rsp)
  0x45525c        488b442420      MOVQ 0x20(SP), AX       # 把参数b的值放入 AX=2
  0x455261        4889442418      MOVQ AX, 0x18(SP)       # 把参数b的值和参数a交换a=2
  0x455266        488b0424        MOVQ 0(SP), AX          # 取出临时存放的a的值1
  0x45526a        4889442420      MOVQ AX, 0x20(SP)       # 把临时存放a的值复制给b=1
}
  0x45526f        488b6c2408      MOVQ 0x8(SP), BP        # 恢复main.main的BP
  0x455274        4883c410        ADDQ $0x10, SP          # 调整栈大小
  0x455278        c3              RET                     # 函数返回

图片备注:图片+08处的runtime.main BP应该是main.main BP

  1. 交换两个变量的值(引用传递)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

func main() {
    a,b := 1,2
    swap(&a,&b)
}

//go:noinline
func swap(a,b *int) {
    *a,*b = *b,*a
}

相关汇编代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
  0x4551e0        493b6610            CMPQ 0x10(R14), SP
  0x4551e4        7639                JBE 0x45521f
  0x4551e6        4883ec28            SUBQ $0x28, SP
  0x4551ea        48896c2420          MOVQ BP, 0x20(SP)
  0x4551ef        488d6c2420          LEAQ 0x20(SP), BP
    a,b := 1,2
  0x4551f4        48c744241801000000  MOVQ $0x1, 0x18(SP)
  0x4551fd        48c744241002000000  MOVQ $0x2, 0x10(SP)
    swap(&a,&b)
  0x455206        488d442418          LEAQ 0x18(SP), AX   # AX=0x18(SP) -> 0x1    注意这里的0x18偏移量是变量a的地址
  0x45520b        488d5c2410          LEAQ 0x10(SP), BX   # BX=0x10(SP) -> 0x2    注意这里的0x10偏移量是变量a的地址
  0x455210        e82b000000          CALL main.swap(SB)  # 调用main.swap函数
}
  0x455215        488b6c2420          MOVQ 0x20(SP), BP
  0x45521a        4883c428            ADDQ $0x28, SP
  0x45521e        c3                  RET
func main() {
  0x45521f        90                  NOPL
  0x455220        e83bcdffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x455225        ebb9                JMP main.main(SB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func swap(a,b *int) {
  0x455240        4883ec10        SUBQ $0x10, SP
  0x455244        48896c2408      MOVQ BP, 0x8(SP)
  0x455249        488d6c2408      LEAQ 0x8(SP), BP
  0x45524e        4889442418      MOVQ AX, 0x18(SP)
  0x455253        48895c2420      MOVQ BX, 0x20(SP)
    *a,*b = *b,*a
  0x455258        8400            TESTB AL, 0(AX)
  0x45525a        488b00          MOVQ 0(AX), AX      # 取AX=0x1
  0x45525d        48890424        MOVQ AX, 0(SP)
  0x455261        488b442418      MOVQ 0x18(SP), AX   # AX=0x30(SP)
  0x455266        8400            TESTB AL, 0(AX)
  0x455268        488b4c2420      MOVQ 0x20(SP), CX   # CX=0x28(SP)
  0x45526d        8401            TESTB AL, 0(CX)
  0x45526f        488b09          MOVQ 0(CX), CX      # CX=0x2
  0x455272        488908          MOVQ CX, 0(AX)      # AX=0x30(SP) -> 0x2
  0x455275        488b442420      MOVQ 0x20(SP), AX   # AX=0x28(SP)
  0x45527a        8400            TESTB AL, 0(AX)
  0x45527c        488b0c24        MOVQ 0(SP), CX      # CX=0x1
  0x455280        488908          MOVQ CX, 0(AX)      # AX=0x28(SP) -> 0x1
}
  0x455283        488b6c2408      MOVQ 0x8(SP), BP
  0x455288        4883c410        ADDQ $0x10, SP
  0x45528c        c3              RET

图片备注:图片+08处的runtime.main BP应该是main.main BP

  1. 交换两个变量的值(引用传递)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

func main() {
    a,b := 1,2
    swap(&a,&b)
    //println(a, b)	// 1,2
}

//go:noinline
func swap(a,b *int) {
    // temp = a
    // a = b
    // b = temp
    // 变量a、b是函数内的局部变量,
    // 因此交换不会影响外部指针数据
    a,b = b,a	// 这种情况和第一种副本传参交换一致

    //println(*a, *b)	// 2,1
}

相关汇编代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
  0x4551e0        493b6610            CMPQ 0x10(R14), SP
  0x4551e4        7639                JBE 0x45521f
  0x4551e6        4883ec28            SUBQ $0x28, SP
  0x4551ea        48896c2420          MOVQ BP, 0x20(SP)
  0x4551ef        488d6c2420          LEAQ 0x20(SP), BP
    a,b := 1,2
  0x4551f4        48c744241801000000  MOVQ $0x1, 0x18(SP) # 变量a
  0x4551fd        48c744241002000000  MOVQ $0x2, 0x10(SP) # 变量b
    swap(&a,&b)
  0x455206        488d442418          LEAQ 0x18(SP), AX   # 参数a
  0x45520b        488d5c2410          LEAQ 0x10(SP), BX   # 参数b
  0x455210        e82b000000          CALL main.swap(SB)  # 函数调用
}
  0x455215        488b6c2420          MOVQ 0x20(SP), BP
  0x45521a        4883c428            ADDQ $0x28, SP
  0x45521e        c3                  RET
func main() {
  0x45521f        90                  NOPL
  0x455220        e83bcdffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x455225        ebb9                JMP main.main(SB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func swap(a,b *int) {
  0x455240        4883ec10        SUBQ $0x10, SP
  0x455244        48896c2408      MOVQ BP, 0x8(SP)
  0x455249        488d6c2408      LEAQ 0x8(SP), BP
  0x45524e        4889442418      MOVQ AX, 0x18(SP)
  0x455253        48895c2420      MOVQ BX, 0x20(SP)
    a,b = b,a
  0x455258        48890424        MOVQ AX, 0(SP)      # AX寄存器参数a保存在临时容器里
  0x45525c        488b442420      MOVQ 0x20(SP), AX   # AX=0x28(SP)   S.b
  0x455261        4889442418      MOVQ AX, 0x18(SP)   # 交换
  0x455266        488b0424        MOVQ 0(SP), AX
  0x45526a        4889442420      MOVQ AX, 0x20(SP)
}
  0x45526f        488b6c2408      MOVQ 0x8(SP), BP
  0x455274        4883c410        ADDQ $0x10, SP
  0x455278        c3              RET

图片备注:图片+08处的runtime.main BP应该是main.main BP

返回值

  1. 匿名返回值。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

func main() {
    var a int = 1
    b := incr(a)
    _ = b
}

//go:noinline
func incr(a int) int {
    var b int = 2

    // defer 闭包捕获的是incr局部变量
    defer func() {
        a++
        b++
    }()

    a++
    b = a
    return b // 由于b在incr栈空间创建defer影响不到
}

相关汇编代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {
  0x4552a0        493b6610            CMPQ 0x10(R14), SP
  0x4552a4        7630                JBE 0x4552d6
  0x4552a6        4883ec20            SUBQ $0x20, SP
  0x4552aa        48896c2418          MOVQ BP, 0x18(SP)
  0x4552af        488d6c2418          LEAQ 0x18(SP), BP
    var a int = 1
  0x4552b4        48c744241001000000  MOVQ $0x1, 0x10(SP)
    b := incr(a)
  0x4552bd        b801000000          MOVL $0x1, AX       # AX=1
  0x4552c2        e819000000          CALL main.incr(SB)
  0x4552c7        4889442408          MOVQ AX, 0x8(SP)
}
  0x4552cc        488b6c2418          MOVQ 0x18(SP), BP
  0x4552d1        4883c420            ADDQ $0x20, SP
  0x4552d5        c3                  RET
func main() {
  0x4552d6        e825cdffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x4552db        ebc3                JMP main.main(SB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
func incr(a int) int {
  0x4552e0        4c8d6424f8          LEAQ -0x8(SP), R12
  0x4552e5        4d3b6610            CMPQ 0x10(R14), R12
  0x4552e9        0f86d9000000        JBE 0x4553c8
  0x4552ef        4881ec88000000      SUBQ $0x88, SP
  0x4552f6        4889ac2480000000    MOVQ BP, 0x80(SP)
  0x4552fe        488dac2480000000    LEAQ 0x80(SP), BP
  0x455306        4889842490000000    MOVQ AX, 0x90(SP)
  0x45530e        48c744240800000000  MOVQ $0x0, 0x8(SP)
    var b int = 2
  0x455317        48c744241002000000  MOVQ $0x2, 0x10(SP)
    defer func() {
  0x455320        48c744246800000000  MOVQ $0x0, 0x68(SP)
  0x455329        440f117c2470        MOVUPS X15, 0x70(SP)
  0x45532f        488d4c2468          LEAQ 0x68(SP), CX           # CX=0x68(SP)
  0x455334        48894c2460          MOVQ CX, 0x60(SP)
  0x455339        8401                TESTB AL, 0(CX)
  0x45533b        488d159e000000      LEAQ main.incr.func1(SB), DX  # DX=main.incr.func1
  0x455342        4889542468          MOVQ DX, 0x68(SP)
  0x455347        8401                TESTB AL, 0(CX)
  0x455349        488d942490000000    LEAQ 0x90(SP), DX           # DX=0x90(SP)
  0x455351        4889542470          MOVQ DX, 0x70(SP)
  0x455356        8401                TESTB AL, 0(CX)
  0x455358        488d542410          LEAQ 0x10(SP), DX           # DX=0x10(SP)
  0x45535d        4889542478          MOVQ DX, 0x78(SP)
  0x455362        48894c2430          MOVQ CX, 0x30(SP)
  0x455367        488d442418          LEAQ 0x18(SP), AX           # AX=0x18(SP)
  0x45536c        e88f50fdff          CALL runtime.deferprocStack(SB) # 注册defer链表
  0x455371        85c0                TESTL AX, AX
  0x455373        7539                JNE 0x4553ae
  0x455375        eb00                JMP 0x455377
    a++
  0x455377        488b842490000000    MOVQ 0x90(SP), AX   # AX=1
  0x45537f        48ffc0              INCQ AX             # AX=2
  0x455382        4889842490000000    MOVQ AX, 0x90(SP)
    b = a
  0x45538a        4889442410          MOVQ AX, 0x10(SP)
    return b // 由于b在incr栈空间创建defer影响不到
  0x45538f        4889442408          MOVQ AX, 0x8(SP)    # return先把值复制给I.o然后在去defer的
  0x455394        e88756fdff          CALL runtime.deferreturn(SB)    # 执行defer,defer捕获的是a和b与I.o无关
  0x455399        488b442408          MOVQ 0x8(SP), AX    # defer完之后在从I.o取值到AX
  0x45539e        488bac2480000000    MOVQ 0x80(SP), BP
  0x4553a6        4881c488000000      ADDQ $0x88, SP
  0x4553ad        c3                  RET
    defer func() {
  0x4553ae        e86d56fdff          CALL runtime.deferreturn(SB)
  0x4553b3        488b442408          MOVQ 0x8(SP), AX
  0x4553b8        488bac2480000000    MOVQ 0x80(SP), BP
  0x4553c0        4881c488000000      ADDQ $0x88, SP
  0x4553c7        c3                  RET
func incr(a int) int {
  0x4553c8        4889442408          MOVQ AX, 0x8(SP)
  0x4553cd        e82eccffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x4553d2        488b442408          MOVQ 0x8(SP), AX
  0x4553d7        e904ffffff          JMP main.incr(SB)

图片备注:图片+80处的runtime.main BP应该是main.main BP

  1. 实名返回值。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package main

func main() {
    var a int = 1
    b := incr(a)
    _ = b
}

//go:noinline
func incr(a int) (b int) {
    // defer 闭包捕获的是incr局部变量
    defer func() {
        a++
        b++
    }()

    a++
    b = a
    return b // 由于b在incr栈空间创建defer影响不到
}

相关汇编代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {
 0x4552a0     493b6610            CMPQ 0x10(R14), SP
 0x4552a4     7630                JBE 0x4552d6
 0x4552a6     4883ec20            SUBQ $0x20, SP
 0x4552aa     48896c2418          MOVQ BP, 0x18(SP)
 0x4552af     488d6c2418          LEAQ 0x18(SP), BP
   var a int = 1
 0x4552b4     48c744241001000000  MOVQ $0x1, 0x10(SP)
   b := incr(a)
 0x4552bd     b801000000          MOVL $0x1, AX       # AX=1
 0x4552c2     e819000000          CALL main.incr(SB)
 0x4552c7     4889442408          MOVQ AX, 0x8(SP)
}
 0x4552cc     488b6c2418          MOVQ 0x18(SP), BP
 0x4552d1     4883c420            ADDQ $0x20, SP
 0x4552d5     c3                  RET
func main() {
 0x4552d6     e825cdffff          CALL runtime.morestack_noctxt.abi0(SB)
 0x4552db     ebc3                JMP main.main(SB)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
func incr(a int) (b int) {
  0x4552e0        493b6610            CMPQ 0x10(R14), SP
  0x4552e4        0f86b8000000        JBE 0x4553a2
  0x4552ea        4883c480            ADDQ $-0x80, SP
  0x4552ee        48896c2478          MOVQ BP, 0x78(SP)
  0x4552f3        488d6c2478          LEAQ 0x78(SP), BP
  0x4552f8        4889842488000000    MOVQ AX, 0x88(SP)
  0x455300        48c744240800000000  MOVQ $0x0, 0x8(SP)
    defer func() {
  0x455309        48c744246000000000  MOVQ $0x0, 0x60(SP)
  0x455312        440f117c2468        MOVUPS X15, 0x68(SP)    # 清空0x68(SP)后16B
  0x455318        488d4c2460          LEAQ 0x60(SP), CX       # CX=0x60(SP)
  0x45531d        48894c2458          MOVQ CX, 0x58(SP)
  0x455322        8401                TESTB AL, 0(CX)
  0x455324        488d1595000000      LEAQ main.incr.func1(SB), DX    # DX=main.incr.func1
  0x45532b        4889542460          MOVQ DX, 0x60(SP)
  0x455330        8401                TESTB AL, 0(CX)
  0x455332        488d942488000000    LEAQ 0x88(SP), DX       # DX=0x88(SP)
  0x45533a        4889542468          MOVQ DX, 0x68(SP)
  0x45533f        8401                TESTB AL, 0(CX)
  0x455341        488d542408          LEAQ 0x8(SP), DX        # DX=0x8(SP)
  0x455346        4889542470          MOVQ DX, 0x70(SP)
  0x45534b        48894c2428          MOVQ CX, 0x28(SP)
  0x455350        488d442410          LEAQ 0x10(SP), AX   # AX=0x10(SP),defer结构体起始地址
  0x455355        e8a650fdff          CALL runtime.deferprocStack(SB) # 注册defer
  0x45535a        85c0                TESTL AX, AX
  0x45535c        7530                JNE 0x45538e
  0x45535e        6690                NOPW
  0x455360        eb00                JMP 0x455362
    a++
  0x455362        488b842488000000    MOVQ 0x88(SP), AX   # AX=1
  0x45536a        48ffc0              INCQ AX             # AX=2
  0x45536d        4889842488000000    MOVQ AX, 0x88(SP)
    b = a
  0x455375        4889442408          MOVQ AX, 0x8(SP)
    return b // 由于b在incr栈空间创建defer影响不到
  0x45537a        e8a156fdff          CALL runtime.deferreturn(SB)    # 执行defer链表
  0x45537f        488b442408          MOVQ 0x8(SP), AX    # 从I.o中取出返回值放入AX
  0x455384        488b6c2478          MOVQ 0x78(SP), BP
  0x455389        4883ec80            SUBQ $-0x80, SP
  0x45538d        c3                  RET
    defer func() {
  0x45538e        e88d56fdff          CALL runtime.deferreturn(SB)
  0x455393        488b442408          MOVQ 0x8(SP), AX
  0x455398        488b6c2478          MOVQ 0x78(SP), BP
  0x45539d        4883ec80            SUBQ $-0x80, SP
  0x4553a1        c3                  RET
func incr(a int) (b int) {
  0x4553a2        4889442408          MOVQ AX, 0x8(SP)
  0x4553a7        e854ccffff          CALL runtime.morestack_noctxt.abi0(SB)
  0x4553ac        488b442408          MOVQ 0x8(SP), AX
  0x4553b1        e92affffff          JMP main.incr(SB)

图片备注:图片+78处的runtime.main BP应该是main.main BP

寻址方式

  1. 如果把整个函数栈帧视为一个struct,SP存储着这个struct的起始地址,然后就可以通过【基址+位移】的方式来寻址struct的各个字段,也就是栈帧上的局部变量、参数和返回值。
  2. 在Go汇编中,寄存器的名字没有位数之分,比如AX寄存器没有什么RAX、EAX之类的名字,指令中一律只能使用AX。所以如果指令中有操作的寄存器或是指令需要访问内存,则操作码都需要带上后缀 B(8位)、W(16位)、D(32位)、Q(64位)。
  3. Go 语言中函数的返回值可以是匿名的,也可以是命名的。
    • 对于匿名返回值而言,只能通过return语句为返回值赋值。
    • 对于命名返回值,可以在代码中通过其名称直接操作,与参数和局部变量类似。

调用约定

  1. 在进行函数调用的时候,调用者需要把参数传递给被调用者,而被调用者也要把返回值回传给调用者。
  2. 调用约定就是用来规范参数和返回值的传递问题的。如果基于栈传递还会规定栈空间由谁负责分配、释放。
  3. 调用约定:
    • 返回值和参数都通过栈传递,对应的栈空间由调用者负责分配和释放。
    • 返回值和参数在栈上的布局等价于两个struct,struct的起始地址按照平台机器字节长度对齐。

寄存器传参

  1. Go在1.17版本前都是采用的栈的形式传递参数和返回值。这样实现简单且能支持海量的参数传递,缺点就是与寄存器传参相比性能方面会差一些。
  2. 在1.17版本后Go采用了寄存器传参,当然只是在部分硬件架构上实现了。即使有16个通用寄存器的amd64架构,可用于传参的寄存器也是有上限的,参数太多时还是有一部分通过栈传递。
  3. Go采用AXBXCXDISIR8R9R10R11这9个寄存器依顺序传递参数或返回值,多有的参数或返回值则时通过栈传递的方式。
  4. 总体来讲,使用9个通用寄存器传递参数进行优化,最多只能传递9个机器字节大小,而不是9个参数或返回值。像string会占用两个机器字节,slice则会占用三个机器字节。

参考

  1. https://mp.weixin.qq.com/s/zcqzarXMJrDUY5DLXZXY1Q