引出问题
网上看到一个问题,如下代码最后输出是什么,答案是选择是 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)
总结
可变参数函数调用方,
- 如果使用了多个参数传入,以多个参数组成一个临时的slice,再传入
- 如果是一个slcie使用了三点操作符,利用已有slice的底层数组另外构造一个临时slice,再传入
可变参数函数被调用方,具体使用时就是一个slice类型的变量。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。