关于go:从汇编看golang可变参数函数底层如何处理

3次阅读

共计 3016 个字符,预计需要花费 8 分钟才能阅读完成。

引出问题

网上看到一个问题,如下代码最初输入是什么,答案是抉择是 A. 18

但解释只有一句,知识点:可变参数函数,感觉不够啊.

//hello.go
package main

import "fmt"

func hello(num ...int) {num[0] = 18
}

func main() {i := []int{5, 6, 7}
    hello(i...)
    fmt.Println(i[0])
}

A. 18
B. 5
C. Compilation error

从汇编解读

$ go tool compile -S -N -l hello.go > 1.txt

第 10 行:i := []int{5, 6, 7}

第 11 行:hello(i...)

"".hello STEXT nosplit size=70 args=0x18 locals=0x18 funcid=0x0
    //hello 函数应用了调用方的 AX、BX、CX 结构出一个新的 slice
    0x000e 00014 (hello.go:5)    MOVQ    AX, "".num+32(SP)
    0x0013 00019 (hello.go:5)    MOVQ    BX, "".num+40(SP)
    0x0018 00024 (hello.go:5)    MOVQ    CX, "".num+48(SP)"".main STEXT size=438 args=0x0 locals=0xd0 funcid=0x0
    // 因为 slice i 须要的存储空间没有内存逃逸行为,所以能够间接在栈中分配内存
    // 先创立 slice 须要的底层数组,以下 3 行是清空操作
    // 底层数组的地址为 [SP+32, SP+56)
    0x0026 00038 (hello.go:10)    MOVQ    $0, ""..autotmp_3+32(SP) //[SP+32, SP+40)清 0
    0x002f 00047 (hello.go:10)    LEAQ    ""..autotmp_3+40(SP), DX //DX=SP+40
    0x0034 00052 (hello.go:10)    MOVUPS    X15, (DX) //X15 没找到在哪,应该是个 0;给 DX 所指向地址开始的 16 个字节赋 0;即 [SP+40, SP+56) 清 0

    // 底层数组的地址赋值给 AX,以及地址为 SP+72 的内存
    0x0038 00056 (hello.go:10)    LEAQ    ""..autotmp_3+32(SP), AX
    0x003d 00061 (hello.go:10)    MOVQ    AX, ""..autotmp_2+72(SP)
    0x0042 00066 (hello.go:10)    TESTB    AL, (AX)

    // 以下一段在给底层数组每个单元赋值,5、6、7
    0x0044 00068 (hello.go:10)    MOVQ    $5, ""..autotmp_3+32(SP)
    0x004d 00077 (hello.go:10)    TESTB    AL, (AX)
    0x004f 00079 (hello.go:10)    MOVQ    $6, ""..autotmp_3+40(SP)
    0x0058 00088 (hello.go:10)    TESTB    AL, (AX)
    0x005a 00090 (hello.go:10)    MOVQ    $7, ""..autotmp_3+48(SP)
    0x0063 00099 (hello.go:10)    TESTB    AL, (AX)
    0x0065 00101 (hello.go:10)    JMP    103
    
    // 以下一段是生成 slice i
    0x0067 00103 (hello.go:10)    MOVQ    AX, "".i+96(SP) //slice 对应的底层数组地址
    0x006c 00108 (hello.go:10)    MOVQ    $3, "".i+104(SP) //slice 长度
    0x0075 00117 (hello.go:10)    MOVQ    $3, "".i+112(SP) //slice 容量

    //hello(num ...int)函数能够接管多个参数
    // 三点操作符在这里起的作用是,相当于将原有 slice i 拆成多个参数传递
    // 变成 hello(5, 6, 7)
    // 再依据这多个参数生成一个长期 slice,因为没有逃逸,也是在以后栈中创立
    // 另,golang 编译阶段应该有判断,因为传入应用了三点操作符
    // 所以新的长期 slice 间接利用了已有 slice 的底层数组
    //hello 函数须要调用到的变量 AX(slice 底层数组地址),BX(slice 大小),CX(slice 容量)
    // 因为 AX 曾经是了,以下是解决 BX 和 CX
    0x007e 00126 (hello.go:11)    MOVL    $3, BX
    0x0083 00131 (hello.go:11)    MOVQ    BX, CX
    0x0086 00134 (hello.go:11)    PCDATA    $1, $1
    0x0086 00134 (hello.go:11)    CALL    "".hello(SB)

以下是将第 11 行改为 hello(5, 6, 7) 后的局部编译后果

    0x0087 00135 (hello.go:11)    MOVQ    $0, ""..autotmp_1+56(SP)
    0x0090 00144 (hello.go:11)    LEAQ    ""..autotmp_1+64(SP), DX
    0x0095 00149 (hello.go:11)    MOVUPS    X15, (DX)

    // 底层数组地址赋值给 AX
    0x0099 00153 (hello.go:11)    LEAQ    ""..autotmp_1+56(SP), AX
    0x009e 00158 (hello.go:11)    MOVQ    AX, ""..autotmp_6+88(SP)
    0x00a3 00163 (hello.go:11)    TESTB    AL, (AX)

    // 底层数组各单元赋值
    0x00a5 00165 (hello.go:11)    MOVQ    $5, ""..autotmp_1+56(SP)
    0x00ae 00174 (hello.go:11)    TESTB    AL, (AX)
    0x00b0 00176 (hello.go:11)    MOVQ    $6, ""..autotmp_1+64(SP)
    0x00b9 00185 (hello.go:11)    TESTB    AL, (AX)
    0x00bb 00187 (hello.go:11)    MOVQ    $7, ""..autotmp_1+72(SP)
    0x00c4 00196 (hello.go:11)    TESTB    AL, (AX)
    0x00c6 00198 (hello.go:11)    JMP    200

    0x00c8 00200 (hello.go:11)    MOVQ    AX, ""..autotmp_5+176(SP)
    0x00d0 00208 (hello.go:11)    MOVQ    $3, ""..autotmp_5+184(SP)
    0x00dc 00220 (hello.go:11)    MOVQ    $3, ""..autotmp_5+192(SP)

    //hello 函数须要调用到的变量 AX(slice 底层数组地址),BX(slice 大小),CX(slice 容量)
    0x00e8 00232 (hello.go:11)    MOVL    $3, BX
    0x00ed 00237 (hello.go:11)    MOVQ    BX, CX
    0x00f0 00240 (hello.go:11)    PCDATA    $1, $1
    0x00f0 00240 (hello.go:11)    CALL    "".hello(SB)

总结

可变参数函数调用方,

  1. 如果应用了多个参数传入,以多个参数组成一个长期的 slice,再传入
  2. 如果是一个 slcie 应用了三点操作符,利用已有 slice 的底层数组另外结构一个长期 slice,再传入

可变参数函数被调用方,具体应用时就是一个 slice 类型的变量。

正文完
 0