&arr 是指向整个数组的指针(如 [5]int),&arr[0] 是指向首元素的指针(int);二者地址值相同但类型不同,不能互换传参,且仅在泛型约束、cgo 或 unsafe 场景下必须使用 *[n]t。

Go 中 &arr 和 &arr[0] 的类型与行为差异
直接取数组地址和取首元素地址,看似等价,实则类型不同、语义不同、传参行为也不同。&arr 得到的是指向整个数组的指针(如 *[5]int),而 &arr[0] 是指向单个元素的指针(*int)。这个区别在函数传参和内存布局中会立刻暴露。
- 若函数参数是
func f(p *[5]int),只能传&arr;传&arr[0]会编译报错:cannot use &arr[0] (type *int) as type *[5]int in argument to f -
&arr指向的是数组整体的起始地址,其值等于&arr[0](地址数值相同),但 Go 类型系统严格区分二者,不自动转换 - 用
unsafe.Sizeof可验证:unsafe.Sizeof(&arr)是指针大小(8 字节),而unsafe.Sizeof(arr)是整个数组大小(如[1000]int就是 8000 字节)
切片底层结构如何“掩盖”了数组指针的使用需求
多数场景下你不需要显式操作数组指针,因为切片([]T)已封装了指向底层数组首地址的指针、长度和容量。它的底层结构等价于:struct { data *T; len, cap int }。这意味着对切片的读写本质上就是通过指针操作底层数组,但无需手动取址或解引用。
- 创建切片时(如
s := arr[:]),s.data自动指向&arr[0],不是&arr - 向函数传切片(
func f(s []int))实际传的是该三元结构的副本,开销固定为 24 字节(64 位系统),远小于传大数组的值拷贝 - 误以为“传切片 = 传数组指针”可能引发误解:切片本身不是指针类型,它是个值类型;只是内部含指针字段
什么时候必须用 *[N]T 而不能用 []T
只有在需要**精确控制数组长度、参与类型约束、或对接 C 函数/unsafe 操作**时,*[N]T 才不可替代。切片无法满足这些硬性要求。
- 泛型约束中要求固定长度:例如
func sum[T ~[5]int](a T) int,此时a是值类型数组,&a才是*[5]int - 调用 C 函数需传递固定尺寸数组指针:C 端声明为
void process(int arr[10]),Go 侧必须传&arr(*[10]int),传切片会触发 cgo 类型检查失败 - 用
unsafe.Slice构造切片时,第一个参数必须是*T,即得自&arr[0],而非&arr—— 这里容易混淆,务必注意入参类型
避免常见内存陷阱:数组值拷贝 vs 指针传递
Go 中数组是值类型,直接赋值或传参会完整复制所有元素。一个 [1e6]int 数组传参,就是 8MB 内存拷贝 —— 这不是优化,是灾难。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
func process(arr [10000]int)→ 每次调用都拷贝 80KB - 正确做法:改为
func process(arr *[10000]int)或更通用的func process(s []int) - 注意边界:
func f(arr [1]string)拷贝成本极低,此时用指针反而增加间接寻址开销,无必要 - 调试技巧:用
go tool compile -S查看汇编,确认是否生成了大块MOVQ或REP MOVSB指令,那是数组值拷贝的信号
*[N]T 是窄门,只在泛型约束、cgo、unsafe 场景下才真正需要伸手进去。










