slice结构体真实定义为type slice struct { array unsafe.Pointer; len int; cap int },其array字段用unsafe.Pointer实现跨类型数组指向,且切片赋值仅复制该三字段。

slice 结构体在 runtime 包里的真实定义
Go 的 slice 不是用户可直接声明的结构体类型,它在运行时由编译器隐式使用,定义位于 $GOROOT/src/runtime/slice.go,实际结构如下:
type slice struct {
array unsafe.Pointer
len int
cap int
}这个结构体不导出,你无法在代码中直接写 var s slice。所有 []T 类型变量在底层都按此三字段布局存放在栈或寄存器中。
为什么指针字段是 unsafe.Pointer
因为 slice 需要能指向任意类型的底层数组(int、string、自定义结构体等),而 Go 的类型系统不允许泛型指针(当时设计时还没泛型)。unsafe.Pointer 是唯一能绕过类型检查、做“通用地址容器”的类型。
- 它不是为了让你手动操作,而是 runtime 内部调度数组内存的必要设计
- 你永远不该在业务代码里对
slice的array字段做unsafe.Pointer转换——这会破坏内存安全 - 所有合法的切片操作(
append、s[i:j]、len/cap函数)都由编译器/运行时接管,自动维护该指针有效性
array 指针不一定指向底层数组开头
这是最容易误解的一点:当你执行 s = arr[2:5],s.array 指向的是 &arr[2],而非 &arr[0]。这意味着:
-
cap(s)不等于底层数组总长度,而是len(arr) - 2 - 修改
s[0]就等于修改arr[2],二者内存完全重叠 - 用
reflect.ValueOf(s).UnsafeAddr()拿到的也不是底层数组首地址,而是s这个结构体本身的地址(即栈上三个字段的起始位置)
别把 slice 结构体和它的底层数组混为一谈
常见错误是认为 “slice 就是数组指针”,其实它是“带长度和容量元信息的指针封装”。关键差异:
- 赋值
s2 := s1:只复制这三个字段(值拷贝),若s1.array和s2.array指向同一块内存,则改元素会互相影响;但改s2的len或追加新元素,不会影响s1.len -
append返回新结构体:扩容时array字段会被更新为新地址,旧变量仍持有原array值 —— 所以必须写s = append(s, x) - nil 切片的
array是nil,len和cap都是 0;空切片(如make([]int, 0))的array非 nil,只是当前没元素
真正难调试的,往往不是结构体长什么样,而是你忘了它只是个轻量描述符 —— 它不保有数据,只管理访问边界。


















