Go 的 copy 函数在重叠内存区域行为安全,底层调用类似 memmove,自动处理读写顺序;它不 panic、不越界,但复制结果取决于切片视图,且仅浅拷贝,不含深拷贝语义。
copy 函数在重叠内存区域的行为是安全的
go 的 copy 函数底层调用类似 c 的 memmove,而非 memcpy,因此天然支持源与目标切片指向同一底层数组且区间重叠的情况。它不会因覆盖顺序错误导致数据损坏——这是编译器内置保障,不是靠用户逻辑兜底。
常见错误现象:有人手动写循环复制(如 for i := range src { dst[i] = src[i] })来替代 copy,一旦 src 和 dst 重叠且方向不对(比如向左移位时从前向后赋值),就会提前覆写尚未读取的源数据。
-
copy(dst, src)自动检测重叠并选择安全拷贝方向(从后往前或从前向后),无需你干预 - 典型安全用例:实现 slice 左移一位
copy(s, s[1:]),或右移填充copy(s[1:], s) - 即使
dst == src(自拷贝),copy直接返回长度、不执行任何操作,避免无意义写入
重叠时的边界行为必须手动理清
安全 ≠ 直观。虽然 copy 不 panic、不越界、不崩内存,但它复制的“结果”取决于你传入的切片视图起始位置和长度,而这些完全由你控制。运行时不会替你判断逻辑是否符合业务预期。
例如:a := []int{1,2,3,4,5},执行 copy(a[2:], a[1:4]) 后,a 变成 [1 2 2 3 4],不是因为 copy 出错,而是因为你让目标从索引 2 开始、源从索引 1 开始、各取 3 个元素——这本身就是一次有明确意图的覆盖式移位。
- 实际复制数量恒为
min(len(src), len(dst)),与底层数组是否重叠无关 - 重叠区域的“谁先读、谁后写”由 runtime 决定,你只需确保
src和dst的切片表达式语义正确 - 调试时别只看最终数组值,要还原出两个切片各自的
len、cap和底层数组起始地址(可用unsafe.SliceHeader辅助,但仅限调试)
为什么不能依赖 copy 实现深拷贝或结构体字段安全复制
copy 是按字节/元素逐个赋值的浅层内存拷贝,对结构体、指针、map、slice 等字段不做递归处理。当源切片元素是结构体且含指针字段时,copy 仅复制指针值本身,目标切片中对应结构体仍指向原内存地址。
常见错误现象:用 copy(dst, src) 复制一个含 *[]byte 字段的结构体切片,之后修改 dst[0].Data[0],发现 src[0].Data[0] 也变了——这不是 copy 的 bug,是它本就不该承担深拷贝职责。
-
copy对[]byte、[]int等基础类型切片是安全且完整的;对含指针/引用字段的复合类型,它只复制“表层” - 若需隔离修改,必须显式遍历 + 手动深拷贝每个字段(如对
struct{ Data []byte },要dst[i].Data = append([]byte(nil), src[i].Data...)) - 字符串转
[]byte时,copy(dst, s)是安全的,但结果仍是独立字节副本,不共享字符串底层数组
忽略返回值会导致重叠逻辑失控
copy 返回实际复制元素数,这个值在重叠场景下尤其关键:它告诉你“这次覆盖真正生效了多少个位置”,而不是“我传了多长的 src”。如果忽略它,你可能误判数据是否完整迁移。
例如:s := []int{1,2,3,4,5},执行 n := copy(s[3:], s),n 是 2(因为 s[3:] 长度为 2),结果 s 变成 [1,2,3,1,2]。若没检查 n,你可能以为复制了全部 5 个元素,进而对后续索引操作产生错误假设。
- 永远用变量接收
copy返回值,尤其在重叠、动态长度或循环批量复制场景中 - 不要假设
len(src) —— 即使你写了 <code>make([]T, len(src)),也可能因并发修改或中间截断导致dst实际长度变小 - 在重叠移位逻辑中,
n常用于计算“剩余未覆盖部分”,比如右移后补零:for i := n; i


















