今天在做一道题的时候发现了这么一个有趣的题目:
func main() {
index := 1
a := []string{"f", "ff", "fff"}
index, a[index-1] = 88888, "ffff"
fmt.Println(a)
}
这个输出是什么呢?我第一眼看的时候感觉会输出panic,因为下标超界,但是运行之后发现没这么简单,运行结果如下
运行之后我惊了,不知道为什么会这样,然后我就输出了汇编看了一下(去掉了一些多余的汇编代码,我们只看赋值那行的汇编):
本篇文章基于go1.15+版本以及mac os 操作系统
//把index的值复制到DX寄存器中
0x00f2 00242 MOVQ "".index+48(SP), DX
// DX寄存器的值-1 在此能看出是先对其进行了-1
0x00f7 00247 DECQ DX
// 把DX寄存器里的值复制autotmp_4+56这个地址上
0x00fa 00250 MOVQ DX, ""..autotmp_4+56(SP)
// 把88888赋值给index+48地址即赋值88888给index
0x00ff 00255 MOVQ $88888, "".index+48(SP)
// 把刚才-1了的值放在AX寄存器中
0x0108 00264 MOVQ ""..autotmp_4+56(SP), AX
// 把a就是那个数组的偏移104个地址的值放入DX寄存器
0x010d 00269 MOVQ "".a+104(SP), DX
// 把a就是那个数组的偏移112个地址的值放入DX寄存器
0x0112 00274 MOVQ "".a+112(SP), CX
// 比较AX寄存器和CX寄存器
0x0117 00279 CMPQ AX, CX
// 如果没溢出就跳到293
0x011a 00282 JCS 293
// 否则
0x011c 00284 NOP
// 跳到605
0x0120 00288 JMP 605
// 逻辑移位(好像是)
0x0125 00293 SHLQ $4, AX
// 把DX的AX*1的地址给CX Ax就是被-1后的那个值存放的地方
0x0129 00297 LEAQ (DX)(AX*1), CX
0x012d 00301 LEAQ 8(CX), CX
0x0131 00305 MOVQ $4, (CX)
// 反正就是之前做了一堆复制和传地址操作把DX的AX*1的地址给了DI
0x0138 00312 LEAQ (DX)(AX*1), DI
·····
// 把ffff这个字符串放到AX寄存器内
0x014a 00330 LEAQ go.string."ffff"(SB), AX
// 从AX寄存器复制出ffff复制到DI就是要修改的数组的那个位置
0x0151 00337 MOVQ AX, (DI)
大概就是这样,其实最根本的原因就是
对
index-1
这个操作早于给index
赋值这个操作,并且提前将其放到了某块地址中。所以后续的数组下标赋值操作是根据这个放在某个地址中的index-1
的值进行的赋值。
总结一下:
这种多重赋值分为两个步骤,有先后顺序:
计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式
作者对汇编不甚了解,如果哪里有错误请大佬指正。