核心概念解析
在 Go 的底层机制中,理解这三种类型的区别至关重要:
- **
类型*:表示具体的指针类型,例如
*int、*string。它们携带了类型信息,编译器会进行严格的类型检查。 - unsafe.Pointer:类似于 C++ 中的
void*,是所有指针类型的基类。任何类型的指针都可以转换为unsafe.Pointer,反之亦然。它主要用于绕过类型系统,实现通用内存操作。 - uintptr:官方定义为一种整数类型,大小足以容纳任意指针的位模式。简单来说,它是一个可以存储地址值的数字,支持加减运算。
为什么需要 uintptr?
既然有了指针和 unsafe.Pointer,为何还需要 uintptr?关键在于算术运算。前两者都不能直接进行加减计算,而在处理内存偏移(offset)时,必须借助 uintptr 来辅助完成地址的数学运算。
实战演示:通过偏移修改结构体字段
下面这段代码展示了如何利用这三个概念访问并修改结构体的私有字段。虽然结构体字段默认不可外部访问,但通过指针偏移我们可以做到这一点。
package main
import (
"fmt"
"unsafe"
)
type Person struct {
age int
name string
}
func main() {
var zhangsan Person = Person{25, "zhangsan"}
// 1. 获取结构体首地址,转为具体类型指针修改 age
// 结构体变量的起始地址即为第一个成员变量的地址
p := (*int)(unsafe.Pointer(&zhangsan))
*p = 35
// 2. 计算 name 字段的偏移量
// unsafe.Pointer 不能直接加减,需先转成 uintptr 进行计算
// unsafe.Sizeof(int(0)) 返回该类型占用的字节数
offset := uintptr(unsafe.Pointer(&zhangsan)) + unsafe.Sizeof(int(0))
// 3. 将计算后的地址转回 unsafe.Pointer,再转为 *string 指针
q := (*string)(unsafe.Pointer(offset))
*q = "lisi"
fmt.Printf("%v", zhangsan)
}

