Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

作用:表示指向任意类型的指针

特殊操作

  1. 任何类型的指针值都可以转换为 Pointer
  2. Pointer 可以转换为任何类型的指针值
  3. uintptr 可以转换为 Pointer
  4. Pointer 可以转换为 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✅ 有效
打印地址Pointeruintptr✅ 有效
指针算术Pointer -> uintptr -> Pointer✅ 有效(有限制)
系统调用调用 syscall 时转换✅ 有效
reflect 转换reflect.Value.Pointer✅ 有效
SliceHeaderData 字段转换✅ 有效
存储 uintptr先存储再转换❌ 无效

大小和对齐

类型大小(64 位)对齐
int811
int1622
int3244
int6488
uintptr88
Pointer88
string168
slice248
interface168

常见模式

// 类型双关
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 类型系统的底层操作:

核心功能

  • 内存布局查询(SizeofAlignofOffsetof
  • 指针运算(Add
  • 类型双关
  • 零拷贝转换(StringSlice

主要类型

  • Pointer:指向任意类型的指针

使用场景

  1. 类型双关(如 float64uint64
  2. 系统调用
  3. 与 C 代码互操作
  4. 性能优化(零拷贝转换)
  5. 实现运行时和标准库

重要警告

  1. 代码可能不可移植
  2. 不受 Go 1 兼容性保证保护
  3. 应极其谨慎使用
  4. 使用 go vet 检查正确性
  5. 遵循 6 种有效的 Pointer 使用模式

使用建议

  1. 仅在必要时使用
  2. 遵循有效的 Pointer 模式
  3. 理解内存对齐和填充
  4. 注意垃圾回收行为
  5. 保持字符串不可变性
  6. 使用 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 包,可以进行底层内存操作,但应谨慎使用,确保遵循有效的使用模式,避免未定义行为。