直接回答:切片是 {指针, len, cap} 三元组,append 时若 len+1 超过 cap,运行时分配更大的新数组、拷贝旧元素、返回新切片头。扩容不是简单翻倍:Go 1.18 之前以 1024 为界,小于它翻倍,大于它约 1.25 倍;1.18 起阈值改为 256,以下翻倍,之后按平滑公式 (cap + 3*256) / 4 迭代,系数从约 2 渐近 1.25,最后还要按元素大小做内存对齐 roundup,所以观察到的 cap 常带「零头」。

展开解析:几个推论值得记住。其一,扩容发生在新数组上,旧切片仍指向旧数组——函数内 append 超容后调用方感受不到新元素,必须回传新切片。其二,扩容有拷贝成本,已知规模时应 make([]T, 0, n) 预分配,热路径上收益明显。其三,大切片只截取一小段会让整个底层数组无法被 GC,需 copy 出小切片。其四,扩容系数是实现细节且版本间改过,不要依赖它写逻辑,容量上唯一的保证是 cap ≥ len。

追问方向:切片截取为什么导致内存泄漏?逃逸分析如何影响切片分配在栈还是堆?(约 486 字)