
大结构体传参必须用指针,因Go按值传递会复制整个结构体,导致高内存分配和GC压力;超64字节或含[]byte、map等字段时应优先用指针,并注意可寻址性与只读约定。
Go 默认按值传递,每次调用函数时都会复制整个结构体。如果结构体包含大量字段(比如几十个 int64、多个 []byte 或嵌套 map),一次复制可能触发数 KB 甚至 MB 级内存分配,还会增加 GC 压力。这不是“建议用指针”,而是“不用指针就容易出性能问题”。
常见错误现象:
– CPU 分析显示 runtime.memmove 占比异常高
– pprof 显示某函数的调用栈中堆分配陡增
– 并发压测时吞吐量卡在某个阈值上不去,且与结构体大小强相关
不要凭感觉判断“大”。用 unsafe.Sizeof 测真实大小,尤其注意隐式开销:
[]T)本身是 24 字节(头 + len + cap),但底层数组内存不计入 Sizeof
string)是 16 字节(ptr + len),内容在堆上独立分配实操建议:
– 对疑似大结构体,加一行日志:
fmt.Printf("size of MyStruct: %d bytes\n", unsafe.Sizeof(MyStruct{}))[]byte、map[string]interface{}、sync.Mutex(虽小但禁止拷贝),必须用指针
立即学习“go语言免费
学习笔记(深入)”;
不是所有值都能取地址 —— 字面量、函数返回值、map 中的 value 默认不可寻址,直接传指针会编译报错:cannot take the address of ...
典型场景和绕过方式:
– m := make(map[string]User); u := m["x"]; foo(&u) ❌ 不合法(map value 不可寻址)
– 正确做法:
u := m["x"]
m["x"] = u // 先读再写,确保 u 是局部变量
foo(&u)
user := db.GetUser(id); handleUser(&user) ✅ 安全handleUser(&db.GetUser(id)) ❌ 编译失败(函数返回值不可取地址)
用指针是为了避免复制,不是为了“方便改”。多数场景下,你只希望读,不希望副作用。Go 没有 const 指针语法,所以靠约定和防御:
ProcessUser(u *User) 暗示可变,ValidateUser(u *User) 应只读u = &(*u) 强制复制一份(仅当结构体小到可接受)interface{ GetID() int } 抽象容易被忽略的一点:即使你没写 u.Name = "xxx",某些方法(如 json.Unmarshal、proto.Unmarshal)内部会直接改指针所指内存。传参前务必确认被调用方的行为契约。