数组变量直接持有连续数据块,值类型,复制开销大;slice变量仅存24字节header(指针、len、cap),是底层数组的引用视图,共享数据、可动态扩容。

Go 语言中 slice 和数组的内存布局根本不同:数组变量直接持有连续数据块;slice 变量只存一个三字段 header(指针、len、cap),真实数据在别处。
数组内存布局:变量即数据
声明 [5]int 时,编译器在栈(或逃逸到堆)上直接分配 5 × 8 = 40 字节连续空间,变量名就代表这块内存起始地址。赋值或传参时,整个 40 字节被复制——这是值语义的物理体现。
- 大小完全由类型决定:
[1000]byte占 1KB,[2]struct{a,b int}占 16 字节 - 没有额外元信息:不存长度、不存指针,
len(arr)是编译期常量 - 大数组传参易触发栈溢出或堆逃逸,且拷贝开销肉眼可见
slice 内存布局:header + 底层数组分离
[]int 变量本身只占 24 字节(64 位系统下:8 字节指针 + 8 字节 len + 8 字节 cap),它不包含任何元素数据,只是对底层数组某一段的“视图描述”。
- 底层数组可能位于栈(小切片逃逸分析后)、堆(大容量或生命周期长)或共享于多个 slice 之间
-
slice[1:3]创建新 slice 时,仅复制 header,指针仍指向原底层数组同一块内存 - 修改
s1[0]可能影响s2[0],如果它们共享底层数组且索引重叠——这是共享而非拷贝的直接后果
make([]T, len, cap) 如何影响实际内存分配
调用 make([]int, 0, 100) 会一次性在堆上分配 100 个 int 的底层数组,并返回一个 len=0、cap=100 的 slice header;而 make([]int, 100) 返回的是 len=100、cap=100 的 slice,同样分配了 100 个元素空间,但已预填充零值。
- cap 不等于 len 时,append 只要不超 cap 就不会触发扩容,避免内存重分配
- cap 过大会浪费内存;cap 过小会导致多次 realloc + memcpy,典型表现是 pprof 中
runtime.makeslice占比突增 - 从已有数组创建 slice(如
arr[2:5])不会分配新底层数组,cap 由原数组剩余长度决定
真正容易被忽略的是:slice header 本身可逃逸,但底层数组的生命周期取决于所有引用它的 slice 中最长的那个——哪怕你只保留一个 tiny slice,也可能让几 MB 的底层数组一直驻留内存。











