[3]int是24字节连续值类型,*[3]int是8字节指针;前者自身连续,后者指向的数组才连续;传值拷贝24字节,传指针仅8字节但需确保内存有效。

数组声明时 [3]int 和 *[3]int 的内存布局差异
Go 中数组是值类型,[3]int 本身占 24 字节(3×8),声明即分配连续栈空间;而 *[3]int 是指针,只占 8 字节(64 位系统),指向某处已分配的 [3]int 实例。
关键区别不在“是否连续”,而在“谁负责连续内存”:前者自身就是连续块;后者只是个地址,它指向的目标可能在栈、堆或全局数据区——但只要目标是数组类型,那一片内存仍是连续的。
-
[3]int{1,2,3}:值拷贝时复制全部 24 字节,函数传参开销大 -
*[3]int:传参只传 8 字节指针,但需确保所指内存有效(比如不能指向局部数组后返回) - 用
unsafe.Sizeof可验证:unsafe.Sizeof([3]int{}) == 24,unsafe.Sizeof(&[3]int{}) == 8
切片 []int 和数组指针 *[3]int 混用时的常见 panic
把 *[3]int 当作 []int 用,容易触发 panic: runtime error: slice bounds out of range 或静默越界——因为二者底层结构完全不同。
[]int 是三字段结构(ptr, len, cap),而 *[3]int 就是一个指针。想转成切片必须显式构造:
立即学习“go语言免费学习笔记(深入)”;
arr := [3]int{1,2,3}
ptr := &arr
slice := (*ptr)[:] // ✅ 正确:解引用后切片化
// slice := ptr[:] // ❌ 编译错误:cannot slice *[3]int
- 直接对
*[N]T做[:]会编译失败 - 用
(*ptr)[:]得到长度/容量均为 N 的切片,底层仍指向原数组内存 - 若原数组生命周期结束(如函数内局部数组),
*[N]T成悬垂指针,后续切片访问会引发未定义行为
函数参数用 [3]int 还是 *[3]int?看调用频次和大小
小数组(≤ 4 个基础类型)传值开销小,且避免意外修改原数据;大数组或高频调用场景,必须用指针避免拷贝。
-
func process(a [2]int):每次调用复制 16 字节,几乎无感 -
func process(a [1000]int):每次复制 8KB,压栈+缓存压力明显 -
func process(a *[1000]int):传 8 字节,但调用方必须确保a != nil,否则运行时 panic - 如果函数需要修改原数组内容,只能用
*[N]T;值类型数组内部修改对外不可见
GC 和逃逸分析怎么看待 [3]int 和 *[3]int
[3]int 默认栈分配,除非发生逃逸(比如取地址后返回);*[3]int 所指对象是否逃逸,取决于它指向哪里。
用 go build -gcflags="-m" 看逃逸分析结果:
func f() *[3]int {
a := [3]int{1,2,3}
return &a // a 逃逸到堆
}
- 单独声明
var a [3]int不逃逸;但一旦取地址并传出作用域,整个数组逃逸 -
*[3]int本身不触发 GC,但它指向的数组若在堆上,就参与 GC - 连续性不受影响:无论栈还是堆,Go 的数组内存始终连续
连续性是数组类型的固有属性,不是由声明方式决定的。真正容易被忽略的是:你以为传了指针就能安全共享,结果忘了生命周期管理——那块连续内存早被回收了。










