array是固定栈帧的值类型,编译时分配连续内存并整体拷贝;slice是运行时三字段结构体(指针、len、cap),传参仅拷贝24字节,底层共享数组且支持动态扩容。

Array在汇编中是固定栈帧,Slice是运行时结构体
Go编译器对 array 和 slice 的处理截然不同:前者直接分配固定大小的连续栈空间(或堆上静态块),后者只生成一个三字段结构体——array(指针)、len、cap。你在反汇编时会看到:array[5]int 编译后对应一段 MOVQ $0, (SP) 类似的连续清零指令;而 make([]int, 5) 最终调用的是 runtime.makeslice,返回的是三个寄存器值拼成的结构体。
nil slice 与 empty slice 在汇编里指针值不同
这是最容易被忽略的底层细节:var s []int(nil slice)的汇编表示是三个全零寄存器(AX=0, BX=0, CX=0);而 s := []int{} 或 make([]int, 0)(empty slice)虽然 len 和 cap 都为 0,但 array 字段指向一个全局共享的空地址(如 runtime.zerobase)。所以它们在 == nil 判断时结果不同——汇编层面就是比较第一个字段是否为 0。
- nil slice:三个字段全为 0,
cmp AX, $0就能判等 - empty slice:
array非零(指向zerobase),仅len/cap为 0 - 传参时二者都按 24 字节结构体传递(64 位下),但语义完全不同
slice 截取操作不拷贝内存,只重算指针和长度
s2 := s1[2:4] 这类操作在汇编里几乎无开销:它只是把 s1.array 加上偏移量(2 * sizeof(int))作为新 array,再设新 len 和 cap。你不会看到任何 MEMCPY 或循环指令。这也解释了为什么修改 s2[0] 会同步反映到 s1[2] 上——它们共享同一底层数组起始地址。
- 截取越界会触发
runtime.panicmakeslicecopy,不是编译期错误 -
s[1:1]得到 len=0、cap > 0 的 slice,仍可append - 若原 slice 的
cap不足,append才触发runtime.growslice
数组传参是值拷贝,slice 传参是结构体拷贝
写函数时如果传 [5]int,汇编会把全部 40 字节(64 位 int × 5)压栈或放寄存器;而传 []int 只拷贝 24 字节(指针+两个 int)。这不是“引用传递”的魔法,而是语言规范强制的结构体传值行为。你可以用 go tool compile -S 看到:前者有大量 MOVQ 指令搬数据,后者只有三次 MOVQ 搬结构体字段。
- 数组传参性能差且不可变原数组 → 实际开发中基本不用
- slice 参数修改
len或元素值会影响调用方,但改cap不会(它是只读字段) - 想真正隔离修改?要么传
copy()后的新 slice,要么传指针*[]int
真正难的不是记住 len 和 cap 的区别,而是意识到:所有 slice 行为——扩容、截取、传参——都源于那三个字段在运行时如何被读写。一旦你开始看汇编,就不再依赖“切片是引用类型”这种模糊描述,而是清楚知道哪条指令改了哪个字节。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











