package main
import (
"fmt"
"unsafe"
)
type S1 struct {
A int32
B int64
}
func main() {
s := S1{}
fmt.Println(s)
b := (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.B)))
*b = 1
fmt.Println(s)
}
输出:
{0 0}
{0 1}
像c里面直接操作指针是不可行的,但是go里面提供了unsafe包,可以达到这个目的,慎用,只做了解就可以了。
Pointer
type ArbitraryType int
type Pointer *ArbitraryType
这里特别要注意ArbitraryType在这里只起到文档作用,不是代表是真正的int
-
ArbitraryType is here for the purposes of documentation only and is not actually part of the unsafe package.
-
It represents the type of an arbitrary Go expression.
比如:sizeof等相关的函数可以接受任意类型的变量,假如按照int类型来解释,基本就解释不通了
官方对该类型有四个重要的描述
1.任何类型的指针值都可以转换为Pointer // unsafe.Pointer(&a1)
2.Pointer可以转换为任何类型的指针值 // (*float)(unsafe.Poniter(&a1))
3.uintptr可以转换为Pointer // unsafe.Poniter(uintptr(nPointer) + unsafe.Sizeof(&a)*3)
4.Pointer可以转换为uintptr // uintptr(unsafe.Pointer(&a1))
总上很像c语言里面的void*
uintptr
type uintptr uintptr
uintptr 是一个整数类型,它足够大,可以存储。只有将Pointer转换成uintptr才能进行指针的相关操作
但下面的用法是错误的
tmp := uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.B)
pb := (*int64)(unsafe.Pointer(tmp))
*pb = 1
uintptr是可以用于指针运算的,但是GC并不把uintptr当做指针,所以uintptr不能持有对象,可能会把GC回收,导致出现无法预知的错误,Pointer指向一个对象时,GC是不会回收这个内存的。
上面错误的代码因为引入一个非指针的临时变量tmp,导致垃圾收集器无法正确识别这个是一个指向变量s的指针。当第二个语句执行时,变量s可能已经被转移,这时候临时变量tmp也就不再是现在的&s.B地址。第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!
相关函数
sizeof
返回类型x所占据的字节数,但是却不包括x所引用的内存,例如,对于一个指针,函数返回的大小为8字节(64位),一个slice的大小则为slice header的大小
var a int32 = 10
t := unsafe.Sizeof(a)
fmt.Println(t)
var b string = "test"
fmt.Println(unsafe.Sizeof(b))
type aa struct {
B bool
C uint64
}
c := aa{}
fmt.Println(unsafe.Sizeof(c))
type bb struct {
B *bool
}
c1 := bb{}
fmt.Println(unsafe.Sizeof(c1))
4// 这个没啥说的, int32在64位机器上占4个字节
16// Sizeof(string)占计算string header的大小, struct string { uint8 *str; int len;}
16// 这个涉及到内存对齐的问题
8// 指针占8个字节(64位机器)
Offsetof
返回结构体中某个字段的偏移量,这个字段必须是structValue.field形式,也就是返回结构体变量的开始位置到那个字段的开始位置之间字节数(包括内存对齐)
type aa struct {
B bool
C uint64
}
c := aa{}
fmt.Println(unsafe.Offsetof(c.C))
输出是8, aa里面8字节对齐, 大小16,每个成员变量8个字节大小
Alignof
这个函数返回内存对齐时的对齐值,跟内存对齐有关,正好可以验证上面的是不是8字节对齐
type aa struct {
B bool
C uint64
}
c := aa{}
fmt.Println(unsafe.Alignof(c))
8
确实是8字节对齐
内存对齐
网上说的很多,在这提一下比较容易忘记的点:有的时候我们调整结构体字段的顺序是可以节省内存的
type Part1 struct {
a bool
b int32
c int8
d int64
e byte
}
type Part2 struct {
e byte
c int8
a bool
b int32
d int64
}
func main() {
part1 := Part1{}
part2 := Part2{}
fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2))
}
$ go run main.go
part1 size: 32, align: 8
part2 size: 16, align: 8