go指针不支持算术运算、地址不保证稳定、nil解引用行为确定、类型转换需经unsafe.pointer中转,这些设计以安全性为代价限制底层控制能力。

Go 的 *T 不支持指针算术,C 的 int* 可以加减偏移
Go 编译器直接禁止对指针做算术运算,比如 p + 1、p++ 在 Go 里是编译错误;而 C 允许你用 ptr + i 手动跳转内存地址。这不是语法糖差异,是设计取舍:Go 要堵死越界访问的常见路径。
实际影响很直接:
- 无法用指针遍历数组(得用
for i := range或切片) - 无法实现类似 C 风格的“内存池游标”或手动结构体字段偏移计算
-
unsafe.Pointer虽能绕过,但必须显式转成uintptr才能加减——且每次转换后不能被 GC 暂停打断,否则可能悬空
Go 的 & 操作符不保证返回变量的“真实地址”,C 的 & 总是返回栈/堆上确切位置
Go 编译器会做逃逸分析,把本该分配在栈上的变量悄悄挪到堆上;更关键的是:它甚至可能把一个变量整个拆开、分散存(比如只把某个字段放寄存器,其余放内存),此时 &x 返回的地址,可能指向一个临时副本,而非原始存储位置。
这意味着:
立即学习“go语言免费学习笔记(深入)”;
- 不能假设
&x的值在整个函数生命周期内稳定(尤其跨 goroutine 传指针时) - 不能拿
&x去和 C 函数交互(C 期望的是连续、稳定的内存块),必须用unsafe.Slice或C.malloc显式分配 - 调试时看到的地址可能和预期不符——不是 bug,是编译器优化结果
Go 的 nil 指针解引用 panic 有确定行为,C 的 NULL 解引用是未定义行为(UB)
Go 运行时只要碰到 *p 且 p == nil,立刻 panic 并给出完整调用栈;C 里 *NULL 是 UB:可能段错误、可能静默读垃圾值、甚至看似正常运行——取决于平台、编译器、内存布局。
这个差异让 Go 更容易定位空指针问题,但也掩盖了更深层的风险:
- Go 的 panic 是运行时拦截,不阻止你在逻辑上误用 nil 指针(比如忘初始化 struct 字段)
- C 的 UB 让静态分析工具(如 Clang Static Analyzer)能提前报警,Go 的 nil panic 只能在运行时暴露
- 跨 CGO 边界时,Go 的
*C.int若为 nil,传给 C 函数仍会触发 C 层的 UB,Go 层的保护失效
Go 的指针不能隐式转换类型,C 的 void* 可以自由转
Go 要求所有指针类型转换必须经由 unsafe.Pointer 中转,比如从 *int 到 *float64 必须写成 (*float64)(unsafe.Pointer(p));C 允许 (float64*)p 直接强转。
这看起来只是多敲几个字,但本质是控制权移交:
- 强制开发者意识到“我在干危险的事”,而不是把类型转换当便利贴用
- 避免因对齐差异导致的总线错误(比如把 1 字节对齐的
uint8指针当 8 字节对齐的float64用) -
unsafe.Pointer本身不能参与算术,进一步限制滥用——想改地址?先转uintptr,再转回来,中间任何 GC 暂停都可能导致地址失效
真正难处理的,从来不是语法能不能写出来,而是当你在 CGO 边界、unsafe 操作、或者调试 GC 行为时,那些编译器没明说、文档没强调、panic 也没报出来的内存假设——它们就藏在 & 和 * 看似简单的背后。










