unsafe 包详解
概述
unsafe 包包含绕过 Go 程序类型安全性的操作。导入 unsafe 的包可能不可移植,并且不受 Go 1 兼容性指南的保护。
主要用途:
- 底层内存操作
- 类型双关(type punning)
- 与 C 代码互操作
- 性能优化
- 实现运行时和标准库
重要警告:
- 使用
unsafe的代码可能不可移植 - 不受 Go 1 兼容性保证保护
- 应谨慎使用,仅在必要时使用
- 使用
go vet检查unsafe用法的正确性
Go 版本要求:所有 Go 版本
包导入
import "unsafe"
类型详解(按 A-Z 分层归类)
A
ArbitraryType
type ArbitraryType int
作用:仅用于文档目的,实际上不是 unsafe 包的一部分。它代表任意 Go 表达式的类型。
说明:
- 这是一个文档占位符类型
- 用于表示可以是任何 Go 类型
示例:
// ArbitraryType 可以代表任何类型
var x int
var y float64
var z string
// unsafe.Sizeof(x), unsafe.Sizeof(y), unsafe.Sizeof(z) 都有效
I
IntegerType
type IntegerType int
作用:仅用于文档目的,实际上不是 unsafe 包的一部分。它代表任何整数类型。
说明:
- 可以是
int,int8,int16,int32,int64 - 也可以是
uint,uint8,uint16,uint32,uint64,uintptr - 无类型常量会被赋予
int类型
示例:
// IntegerType 可以是任何整数类型
var offset int
var index int32
var delta uintptr
// 都可以用于 unsafe.Add 或 unsafe.Slice
P
Pointer
type Pointer *ArbitraryType
作用:表示指向任意类型的指针
特殊操作:
- 任何类型的指针值都可以转换为
Pointer Pointer可以转换为任何类型的指针值uintptr可以转换为PointerPointer可以转换为uintptr
说明:
Pointer允许程序绕过类型系统读写任意内存- 应极其谨慎地使用
- 有 6 种有效的使用模式(详见注意事项)
示例:
// 基本转换
var x int = 42
p := unsafe.Pointer(&x)
// 转换为其他类型
pi := (*int)(p)
fmt.Println(*pi) // 42
// 转换为 uintptr
addr := uintptr(p)
fmt.Printf("Address: %x\n", addr)
函数详解(按 A-Z 分层归类)
A
Add
func Add(ptr Pointer, len IntegerType) Pointer
作用:将 len 加到 ptr 并返回更新后的指针
参数说明:
ptr:原始指针len:要添加的偏移量(必须是整数类型或无类型常量)
返回值:
- 更新后的指针
说明:
- 等价于
Pointer(uintptr(ptr) + uintptr(len)) - 常量 len 参数必须可由
int类型的值表示 - 如果 len 是无类型常量,它会被赋予
int类型 - 运行时如果 len 为负或 ptr 为 nil 且 len 不为零,会发生运行时 panic
- Pointer 的有效使用规则仍然适用
示例:
// 数组元素访问
arr := [5]int{10, 20, 30, 40, 50}
p := unsafe.Pointer(&arr[0])
// 访问第三个元素
p2 := unsafe.Add(p, 2)
v := *(*int)(p2)
fmt.Println(v) // 30
// 结构体字段访问
type S struct {
A int
B int
C int
}
s := S{1, 2, 3}
p = unsafe.Pointer(&s)
pB := unsafe.Add(p, unsafe.Sizeof(int(0)))
b := *(*int)(pB)
fmt.Println(b) // 2
Al
Alignof
func Alignof(x ArbitraryType) uintptr
作用:获取变量 x 所需的对齐要求
参数说明:
x:任意类型的表达式
返回值:
- 对齐要求(以字节为单位)
说明:
- 返回假设通过
var v = x声明的假设变量 v 所需的对齐 - 是使得 v 的地址始终为零模 m 的最大值 m
- 与
reflect.TypeOf(x).Align()返回的值相同 - 特殊情况:如果变量 s 是结构体类型,f 是该结构体内的字段,则
Alignof(s.f)将返回该类型字段在结构体内所需的对齐 - 如果参数类型不具有可变大小,返回值是 Go 常量
示例:
// 基本类型对齐
fmt.Println(unsafe.Alignof(int8(0))) // 1
fmt.Println(unsafe.Alignof(int16(0))) // 2
fmt.Println(unsafe.Alignof(int32(0))) // 4
fmt.Println(unsafe.Alignof(int64(0))) // 8
// 结构体字段对齐
type S struct {
a int8
b int32
c int64
}
var s S
fmt.Println(unsafe.Alignof(s.a)) // 1
fmt.Println(unsafe.Alignof(s.b)) // 4
fmt.Println(unsafe.Alignof(s.c)) // 8
O
Offsetof
func Offsetof(x ArbitraryType) uintptr
作用:返回结构体中字段 x 的偏移量
参数说明:
x:必须是structValue.field形式的表达式
返回值:
- 结构体起始位置到字段起始位置的字节数
说明:
- 返回结构体起始位置和字段起始位置之间的字节数
- 如果参数 x 的类型不具有可变大小,返回值是 Go 常量
- 包括由于字段对齐而引入的任何填充
示例:
// 结构体字段偏移
type S struct {
A int8
B int16
C int32
D int64
}
var s S
fmt.Println(unsafe.Offsetof(s.A)) // 0
fmt.Println(unsafe.Offsetof(s.B)) // 2 (有 1 字节填充)
fmt.Println(unsafe.Offsetof(s.C)) // 4
fmt.Println(unsafe.Offsetof(s.D)) // 8 (有 4 字节填充)
// 总大小
fmt.Println(unsafe.Sizeof(s)) // 16
S
Sizeof
func Sizeof(x ArbitraryType) uintptr
作用:返回变量 x 的大小(以字节为单位)
参数说明:
x:任意类型的表达式
返回值:
- 大小(以字节为单位)
说明:
- 返回假设通过
var v = x声明的假设变量 v 的大小 - 大小不包括 x 可能引用的任何内存
- 如果 x 是切片,返回切片描述符的大小,而不是切片引用的内存大小
- 如果 x 是接口,返回接口值本身的大小,而不是存储在接口中的值的大小
- 对于结构体,大小包括由于字段对齐而引入的任何填充
- 如果参数 x 的类型不具有可变大小,返回值是 Go 常量
- 类型具有可变大小:如果它是类型参数,或者是具有可变大小元素的数组或结构体类型
示例:
// 基本类型大小
fmt.Println(unsafe.Sizeof(int8(0))) // 1
fmt.Println(unsafe.Sizeof(int16(0))) // 2
fmt.Println(unsafe.Sizeof(int32(0))) // 4
fmt.Println(unsafe.Sizeof(int64(0))) // 8
// 指针大小
var p *int
fmt.Println(unsafe.Sizeof(p)) // 8 (64 位系统)
// 切片大小(描述符)
var slice []int
fmt.Println(unsafe.Sizeof(slice)) // 24 (ptr + len + cap)
// 接口大小
var i interface{}
fmt.Println(unsafe.Sizeof(i)) // 16 (type + data)
// 结构体大小(包括填充)
type S struct {
A int8
B int64
}
var s S
fmt.Println(unsafe.Sizeof(s)) // 16 (1 + 7 填充 + 8)
Slice
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
作用:返回一个切片,其底层数组从 ptr 开始,长度和容量为 len
参数说明:
ptr:指向底层数组的指针len:切片的长度和容量
返回值:
- 新切片
说明:
Slice(ptr, len)等价于(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]- 特殊情况:如果 ptr 为 nil 且 len 为零,Slice 返回 nil
- len 参数必须是整数类型或无类型常量
- 常量 len 参数必须是非负的且可由
int类型的值表示 - 运行时如果 len 为负或 ptr 为 nil 且 len 不为零,会发生运行时 panic
示例:
// 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
slice := unsafe.Slice(&arr[0], 5)
fmt.Println(slice) // [1 2 3 4 5]
// 从指针创建切片
ptr := &arr[2]
slice2 := unsafe.Slice(ptr, 3)
fmt.Println(slice2) // [3 4 5]
// nil 指针和零长度
var nilPtr *int
emptySlice := unsafe.Slice(nilPtr, 0)
fmt.Println(emptySlice == nil) // true
// 修改切片会影响底层数组
slice[0] = 100
fmt.Println(arr) // [100 2 3 4 5]
SliceData
func SliceData(slice []ArbitraryType) *ArbitraryType
作用:返回指向参数切片底层数组的指针
参数说明:
slice:输入切片
返回值:
- 指向底层数组的指针
说明:
- 如果
cap(slice) > 0,返回&slice[:1][0] - 如果
slice == nil,返回 nil - 否则,返回指向未指定内存地址的非 nil 指针
示例:
// 获取切片底层数组指针
slice := []int{1, 2, 3, 4, 5}
ptr := unsafe.SliceData(slice)
fmt.Println(*ptr) // 1
// 修改通过指针访问的元素
*ptr = 100
fmt.Println(slice) // [100 2 3 4 5]
// nil 切片
var nilSlice []int
nilPtr := unsafe.SliceData(nilSlice)
fmt.Println(nilPtr == nil) // true
// 空切片(cap > 0)
emptySlice := make([]int, 0, 10)
ptr2 := unsafe.SliceData(emptySlice)
fmt.Println(ptr2 != nil) // true
String
func String(ptr *byte, len IntegerType) string
作用:返回一个字符串值,其底层字节从 ptr 开始,长度为 len
参数说明:
ptr:指向字节数据的指针len:字符串长度
返回值:
- 新字符串
说明:
- len 参数必须是整数类型或无类型常量
- 常量 len 参数必须是非负的且可由
int类型的值表示 - 运行时如果 len 为负或 ptr 为 nil 且 len 不为零,会发生运行时 panic
- Go 字符串是不可变的,只要返回的字符串值存在,传递给 String 的字节就不能被修改
示例:
// 从字节数组创建字符串
data := []byte{'H', 'e', 'l', 'l', 'o'}
s := unsafe.String(&data[0], len(data))
fmt.Println(s) // "Hello"
// 从指针创建字符串
ptr := &data[2]
s2 := unsafe.String(ptr, 3)
fmt.Println(s2) // "llo"
// 修改底层数据会影响字符串(但不应该这样做)
data[0] = 'h'
fmt.Println(s) // "hello" (但这是不安全的)
// 零长度
empty := unsafe.String(&data[0], 0)
fmt.Println(empty == "") // true
StringData
func StringData(str string) *byte
作用:返回指向 str 底层字节的指针
参数说明:
str:输入字符串
返回值:
- 指向底层字节的指针
说明:
- 对于空字符串,返回值未指定,可能为 nil
- Go 字符串是不可变的,StringData 返回的字节不能被修改
示例:
// 获取字符串底层数据指针
s := "Hello"
ptr := unsafe.StringData(s)
// 读取字节(不应该修改)
b := *(*byte)(ptr)
fmt.Println(b) // 72 ('H')
// 空字符串
empty := ""
ptr2 := unsafe.StringData(empty)
fmt.Println(ptr2) // 未指定,可能为 nil
// 遍历字符串字节
for i := 0; i < len(s); i++ {
b := *(*byte)(unsafe.Add(unsafe.Pointer(ptr), i))
fmt.Printf("%c ", b)
}
// 输出:H e l l o
典型示例
1. 类型双关(Type Puning)
package main
import (
"fmt"
"math"
"unsafe"
)
func float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
func uint64bits(u uint64) float64 {
return *(*float64)(unsafe.Pointer(&u))
}
func main() {
f := 3.14159
bits := float64bits(f)
fmt.Printf("Float: %f, Bits: 0x%016x\n", f, bits)
// 反向转换
f2 := uint64bits(bits)
fmt.Printf("Bits: 0x%016x, Float: %f\n", bits, f2)
// 使用标准库验证
fmt.Printf("math.Float64bits: 0x%016x\n", math.Float64bits(f))
}
2. 结构体内存布局
package main
import (
"fmt"
"unsafe"
)
type Struct1 struct {
A int8
B int16
C int32
D int64
}
type Struct2 struct {
D int64
C int32
B int16
A int8
}
func main() {
var s1 Struct1
var s2 Struct2
fmt.Println("Struct1:")
fmt.Printf(" Size: %d\n", unsafe.Sizeof(s1))
fmt.Printf(" A offset: %d, align: %d\n",
unsafe.Offsetof(s1.A), unsafe.Alignof(s1.A))
fmt.Printf(" B offset: %d, align: %d\n",
unsafe.Offsetof(s1.B), unsafe.Alignof(s1.B))
fmt.Printf(" C offset: %d, align: %d\n",
unsafe.Offsetof(s1.C), unsafe.Alignof(s1.C))
fmt.Printf(" D offset: %d, align: %d\n",
unsafe.Offsetof(s1.D), unsafe.Alignof(s1.D))
fmt.Println("\nStruct2:")
fmt.Printf(" Size: %d\n", unsafe.Sizeof(s2))
fmt.Printf(" D offset: %d, align: %d\n",
unsafe.Offsetof(s2.D), unsafe.Alignof(s2.D))
fmt.Printf(" C offset: %d, align: %d\n",
unsafe.Offsetof(s2.C), unsafe.Alignof(s2.C))
fmt.Printf(" B offset: %d, align: %d\n",
unsafe.Offsetof(s2.B), unsafe.Alignof(s2.B))
fmt.Printf(" A offset: %d, align: %d\n",
unsafe.Offsetof(s2.A), unsafe.Alignof(s2.A))
}
3. 使用 Add 进行指针运算
package main
import (
"fmt"
"unsafe"
)
func main() {
// 数组元素访问
arr := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// 获取第一个元素的指针
base := unsafe.Pointer(&arr[0])
// 访问第 5 个元素
ptr5 := unsafe.Add(base, 5*unsafe.Sizeof(arr[0]))
val5 := *(*int)(ptr5)
fmt.Printf("arr[5] = %d\n", val5)
// 遍历数组
for i := 0; i < 10; i++ {
ptr := unsafe.Add(base, i*unsafe.Sizeof(arr[0]))
fmt.Printf("%d ", *(*int)(ptr))
}
fmt.Println()
}
4. 字节切片和字符串转换
package main
import (
"fmt"
"unsafe"
)
// 零拷贝的 []byte 到 string 转换
func bytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
// 零拷贝的 string 到 []byte 转换
func stringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}
func main() {
// []byte 到 string
data := []byte("Hello, World!")
str := bytesToString(data)
fmt.Println(str)
// string 到 []byte
str2 := "Hello, Go!"
bytes := stringToBytes(str2)
fmt.Println(string(bytes))
// 注意:修改底层数据会影响字符串(不安全)
bytes[0] = 'h'
fmt.Println(str2) // 可能打印 "hello, Go!"
}
5. 访问结构体字段
package main
import (
"fmt"
"unsafe"
)
type Person struct {
Name string
Age int
Height float64
}
func main() {
p := Person{Name: "Alice", Age: 30, Height: 1.75}
// 获取结构体基地址
base := unsafe.Pointer(&p)
// 访问 Name 字段
namePtr := unsafe.Add(base, unsafe.Offsetof(p.Name))
name := *(*string)(namePtr)
fmt.Printf("Name: %s\n", name)
// 访问 Age 字段
agePtr := unsafe.Add(base, unsafe.Offsetof(p.Age))
age := *(*int)(agePtr)
fmt.Printf("Age: %d\n", age)
// 访问 Height 字段
heightPtr := unsafe.Add(base, unsafe.Offsetof(p.Height))
height := *(*float64)(heightPtr)
fmt.Printf("Height: %.2f\n", height)
}
6. 实现内存池
package main
import (
"fmt"
"unsafe"
)
type MemoryPool struct {
data []byte
size int
used int
}
func NewMemoryPool(size int) *MemoryPool {
return &MemoryPool{
data: make([]byte, size),
size: size,
used: 0,
}
}
func (p *MemoryPool) Alloc(size int) unsafe.Pointer {
if p.used+size > p.size {
return nil
}
ptr := unsafe.Pointer(unsafe.SliceData(p.data[p.used:]))
p.used += size
return ptr
}
func main() {
pool := NewMemoryPool(1024)
// 分配内存
ptr1 := pool.Alloc(100)
ptr2 := pool.Alloc(200)
fmt.Printf("Allocated at %p\n", ptr1)
fmt.Printf("Allocated at %p\n", ptr2)
// 计算偏移
offset := uintptr(ptr2) - uintptr(ptr1)
fmt.Printf("Offset between allocations: %d bytes\n", offset)
}
7. 类型转换和位操作
package main
import (
"fmt"
"unsafe"
)
func main() {
// int 到 byte 切片
x := int32(0x12345678)
ptr := unsafe.Pointer(&x)
bytes := unsafe.Slice((*byte)(ptr), 4)
fmt.Printf("int32: 0x%08x\n", x)
fmt.Printf("bytes: %v\n", bytes)
// byte 切片到 int
y := *(*int32)(unsafe.Pointer(&bytes[0]))
fmt.Printf("back to int32: 0x%08x\n", y)
// 检查字节序
if bytes[0] == 0x78 {
fmt.Println("Little-endian")
} else if bytes[0] == 0x12 {
fmt.Println("Big-endian")
}
}
8. 使用 Sizeof 进行内存计算
package main
import (
"fmt"
"unsafe"
)
type CacheLine struct {
data [64]byte
}
func main() {
// 计算结构体大小
fmt.Printf("CacheLine size: %d bytes\n",
unsafe.Sizeof(CacheLine{}))
// 计算数组大小
var arr [100]int
fmt.Printf("Array of 100 ints: %d bytes\n",
unsafe.Sizeof(arr))
// 计算每个元素大小
fmt.Printf("Each int: %d bytes\n",
unsafe.Sizeof(arr[0]))
// 计算总大小
total := unsafe.Sizeof(arr[0]) * 100
fmt.Printf("Calculated total: %d bytes\n", total)
// 验证
fmt.Printf("Actual size: %d bytes\n",
unsafe.Sizeof(arr))
}
最佳实践
1. 仅在必要时使用 unsafe
// 推荐:使用标准库
bits := math.Float64bits(f)
// 不推荐:除非必要,避免使用 unsafe
bits := *(*uint64)(unsafe.Pointer(&f))
2. 遵循有效的 Pointer 使用模式
// 推荐:模式 1 - 类型双关
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
// 推荐:模式 2 - 转换为 uintptr 打印
fmt.Printf("Address: %p\n", unsafe.Pointer(&x))
// 不推荐:无效的 Pointer 使用
u := uintptr(unsafe.Pointer(&x))
p := unsafe.Pointer(u) // 无效!
3. 使用 go vet 检查
# 运行 go vet 检查 unsafe 用法
go vet ./...
4. 理解内存对齐
// 推荐:了解结构体对齐
type Optimized struct {
A int64 // 8 bytes
B int32 // 4 bytes
C int16 // 2 bytes + 2 padding
D int8 // 1 byte + 7 padding
} // Total: 24 bytes
5. 注意字符串不可变性
// 推荐:不要修改 StringData 返回的字节
ptr := unsafe.StringData(s)
// 只读访问
b := *ptr
// 不推荐:修改会导致未定义行为
// *ptr = 'x' // 危险!
与其他包配合
reflect 包
import (
"reflect"
"unsafe"
)
// 模式 5:reflect.Value.Pointer 转换
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
// 访问 reflect.SliceHeader
slice := make([]int, 10)
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
data := hdr.Data
syscall 包
import (
"syscall"
"unsafe"
)
// 模式 4:系统调用
var p []byte
syscall.Syscall(syscall.SYS_READ,
uintptr(fd),
uintptr(unsafe.Pointer(&p[0])),
uintptr(len(p)))
runtime 包
import (
"runtime"
"unsafe"
)
// 获取类型信息
var x int
t := reflect.TypeOf(x)
println(t.Size())
注意事项
1. Pointer 的 6 种有效模式
// 模式 1:*T1 到 *T2 的转换(类型双关)
func Float64bits(f float64) uint64 {
return *(*uint64)(unsafe.Pointer(&f))
}
// 模式 2:Pointer 到 uintptr(仅用于打印)
fmt.Printf("Address: %p\n", unsafe.Pointer(&x))
// 模式 3:Pointer -> uintptr -> Pointer(带算术运算)
p = unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + offset)
// 模式 4:调用 syscall 时转换
syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))
// 模式 5:reflect.Value.Pointer 转换
p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
// 模式 6:reflect.SliceHeader/StringHeader Data 字段转换
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
hdr.Data = uintptr(unsafe.Pointer(p))
2. 无效的 Pointer 用法
// 无效:uintptr 存储在变量中
u := uintptr(unsafe.Pointer(p))
p = unsafe.Pointer(u + offset) // 错误!
// 无效:nil 指针转换
u := unsafe.Pointer(nil)
p := unsafe.Pointer(uintptr(u) + offset) // 错误!
// 无效:超出分配范围
end := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s)) // 错误!
3. 垃圾回收注意事项
// uintptr 不是引用
// 转换为 uintptr 后,对象可能被回收
p := unsafe.Pointer(&x)
u := uintptr(p)
// 此时 x 可能被回收!
// 正确:保持 Pointer 类型
p := unsafe.Pointer(&x)
// x 不会被回收
4. 内存对齐和填充
// 结构体字段顺序影响大小
type Bad struct {
A int8 // 1 byte
B int64 // 8 bytes
// 7 bytes padding
} // Total: 16 bytes
type Good struct {
B int64 // 8 bytes
A int8 // 1 byte
// 7 bytes padding ( unavoidable)
} // Total: 16 bytes (same in this case)
// 更好的例子
type Bad2 struct {
A bool // 1 byte
B int64 // 8 bytes
C bool // 1 byte
// 7 bytes padding
} // Total: 24 bytes
type Good2 struct {
B int64 // 8 bytes
A bool // 1 byte
C bool // 1 byte
// 6 bytes padding
} // Total: 16 bytes
5. 字符串不可变性
// String 返回的字符串是不可变的
data := []byte("hello")
s := unsafe.String(&data[0], len(data))
// 修改 data 会影响 s(但不应该这样做)
data[0] = 'H'
fmt.Println(s) // "Hello" (但这是不安全的)
// 正确:复制数据
data2 := append([]byte(nil), data...)
s2 := unsafe.String(&data2[0], len(data2))
6. 可移植性问题
// unsafe 代码可能不可移植
// 不同架构的大小和对齐可能不同
// 推荐:使用 Sizeof 和 Alignof 获取实际值
size := unsafe.Sizeof(int(0)) // 可能是 4 或 8
// 不推荐:硬编码大小
const intSize = 8 // 在 32 位系统上错误
7. 性能考虑
// unsafe 操作通常很快,但要小心
// 推荐:零拷贝转换
func bytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
// 不推荐:过度使用可能导致优化问题
// 编译器可能无法优化 unsafe 代码
快速参考
类型速查表
| 类型 | 说明 |
|---|---|
Pointer | 指向任意类型的指针 |
ArbitraryType | 任意 Go 表达式类型(文档用) |
IntegerType | 任意整数类型(文档用) |
函数速查表
| 函数 | 说明 |
|---|---|
Alignof | 获取对齐要求 |
Offsetof | 获取字段偏移量 |
Sizeof | 获取大小 |
Add | 指针加法 |
Slice | 从指针创建切片 |
SliceData | 获取切片底层指针 |
String | 从字节创建字符串 |
StringData | 获取字符串底层指针 |
Pointer 使用模式
| 模式 | 说明 | 有效性 |
|---|---|---|
| 类型双关 | *T1 到 *T2 | ✅ 有效 |
| 打印地址 | Pointer 到 uintptr | ✅ 有效 |
| 指针算术 | Pointer -> uintptr -> Pointer | ✅ 有效(有限制) |
| 系统调用 | 调用 syscall 时转换 | ✅ 有效 |
| reflect 转换 | reflect.Value.Pointer | ✅ 有效 |
| SliceHeader | Data 字段转换 | ✅ 有效 |
| 存储 uintptr | 先存储再转换 | ❌ 无效 |
大小和对齐
| 类型 | 大小(64 位) | 对齐 |
|---|---|---|
int8 | 1 | 1 |
int16 | 2 | 2 |
int32 | 4 | 4 |
int64 | 8 | 8 |
uintptr | 8 | 8 |
Pointer | 8 | 8 |
string | 16 | 8 |
slice | 24 | 8 |
interface | 16 | 8 |
常见模式
// 类型双关
bits := *(*uint64)(unsafe.Pointer(&floatVal))
// 指针算术
p = unsafe.Add(base, offset)
// 结构体字段访问
field := *(*T)(unsafe.Add(base, unsafe.Offsetof(s.field)))
// 零拷贝转换
str := unsafe.String(unsafe.SliceData(bytes), len(bytes))
bytes := unsafe.Slice(unsafe.StringData(str), len(str))
// 获取地址
addr := uintptr(unsafe.Pointer(&x))
总结
unsafe 包提供了绕过 Go 类型系统的底层操作:
核心功能:
- 内存布局查询(
Sizeof、Alignof、Offsetof) - 指针运算(
Add) - 类型双关
- 零拷贝转换(
String、Slice)
主要类型:
Pointer:指向任意类型的指针
使用场景:
- 类型双关(如
float64到uint64) - 系统调用
- 与 C 代码互操作
- 性能优化(零拷贝转换)
- 实现运行时和标准库
重要警告:
- 代码可能不可移植
- 不受 Go 1 兼容性保证保护
- 应极其谨慎使用
- 使用
go vet检查正确性 - 遵循 6 种有效的 Pointer 使用模式
使用建议:
- 仅在必要时使用
- 遵循有效的 Pointer 模式
- 理解内存对齐和填充
- 注意垃圾回收行为
- 保持字符串不可变性
- 使用
go vet验证代码
典型用法:
// 类型双关
bits := *(*uint64)(unsafe.Pointer(&f))
// 指针运算
p = unsafe.Add(base, offset)
// 零拷贝转换
str := unsafe.String(unsafe.SliceData(bytes), len(bytes))
// 内存布局查询
size := unsafe.Sizeof(x)
align := unsafe.Alignof(x)
offset := unsafe.Offsetof(s.field)
通过 unsafe 包,可以进行底层内存操作,但应谨慎使用,确保遵循有效的使用模式,避免未定义行为。