Go禁止指针运算,仅支持取地址(&)和解引用(*),且有严格限制:不可对临时值、map元素、函数返回值等取址;解引用前须判空;指针不可比较或作map键;unsafe.Pointer应谨慎使用。

Go 里没有指针运算,& 和 * 不是 C 那套玩法
Go 明确禁止指针算术(pointer arithmetic),比如 p + 1、p++、uintptr(p) + size(除非你显式转成 uintptr 并绕过安全检查,但这是 unsafe 行为)。所以“指针运算”在 Go 中是个误导性说法——你真正能做的只有取地址和解引用。
常见错误现象:写 p++ 报错 invalid operation: p++ (non-numeric type *int);或尝试 &a[0] + 1 直接编译失败。
-
&x只能用于可寻址的值(变量、结构体字段、切片元素等),不能用于字面量或函数调用结果(如&fmt.Sprintf("x")报错) -
*p要求p是指针类型,且不为nil,否则运行时 panic:panic: runtime error: invalid memory address or nil pointer dereference - 指针类型本身不可比较(除与
nil),也不能作为 map key
& 取地址的合法场景和陷阱
取地址不是万能的。Go 编译器会拒绝给临时值或不可寻址对象取地址,这是为了防止悬垂指针。
使用场景:
立即学习“go语言免费学习笔记(深入)”;
- 传参时避免拷贝大结构体:
func process(s *HeavyStruct) - 修改函数外的变量:
func increment(x *int) { *x++ } - 构建链表、树等数据结构节点
容易踩的坑:
- 对切片字面量直接取地址:
&[]int{1,2,3}❌ 编译报错;应先赋值再取址:arr := []int{1,2,3}; p := &arr - 对 map 的值取地址:
&m["key"]❌ 不合法(map 元素不可寻址);需先拷贝到局部变量:v := m["key"]; p := &v
- 对函数返回值取地址(除非返回的是变量):
&someFunc()❌ 大概率报错;只有返回变量地址才安全,比如&globalVar
* 解引用前必须确认非 nil,且类型匹配
解引用是运行时行为,编译器不检查是否为 nil,全靠程序员防御。
典型错误现象:程序突然 crash,堆栈指向某行 *p,但没做空检查。
- 永远在解引用前判断:
if p != nil { val := *p } - 指针类型严格匹配:
*int不能解引用为*int64,哪怕底层都是整数;类型转换需显式:var p *int; q := (*int64)(unsafe.Pointer(p))
(仅限unsafe场景) - 结构体指针解引用后才能访问字段:
ps := &S{x: 1}; ps.x等价于(*ps).x,但不能写成*ps.x(运算符优先级问题,实际是*(ps.x))
什么时候真需要 unsafe.Pointer?别轻易碰
极少数场景绕不开:实现自定义内存池、与 C 交互、反射中获取结构体首地址、字节切片和字符串互转(如 string(unsafe.Slice(...)))。
但代价明确:
- 失去 Go 的内存安全保证,越界读写直接 crash 或静默损坏数据
- GC 可能无法追踪通过
unsafe.Pointer持有的内存,导致提前回收 - 代码无法在 race detector 下可靠运行
- Go 版本升级可能破坏依赖
unsafe的逻辑(官方不承诺兼容)
替代方案优先考虑:reflect.SliceHeader / reflect.StringHeader(仍需 unsafe)、标准库的 bytes 和 strings 工具函数、或重构为纯 safe 代码。
真正的难点不在语法,而在理解 Go 的内存模型边界——它用限制换安全,而开发者得学会在边界内把事情做对。










