unsafe 库让 golang 可以像C语言一样 *** 作计算机内存,但这并不是golang推荐使用的,能不用尽量不用,就像它的名字所表达的一样,它绕过了golang的内存安全原则,是不安全的,容易使你的程序出现莫名其妙的问题,不利于程序的扩展与维护。
先简单介绍下Golang指针类型:
*类型
:普通指针,用于传递对象地址,不能进行指针运算。unsafe.Pointer
:通用指针类型,用于转换不同类型的指针,不能进行指针运算。uintptr
:用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象,uintptr 类型的目标会被回收。unsafe.Pointer 可以和 普通指针 进行相互转换。
unsafe.Pointer 可以和 uintptr 进行相互转换。
也就是说 unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。
unsafe底层源码如下:
两个类型:
// go 1.14 src/unsafe/unsafe.gotype ArbitraryType inttype Pointer *ArbitraryType
ArbitraryType是int的一个别名,在Go中对ArbitraryType赋予特殊的意义。代表一个任意Go表达式类型。
Pointer 是 int指针类型 的一个别名,在Go中可以把Pointer类型,理解成任何指针的父类型。
三个函数:
func Sizeof(x ArbitraryType) uintptrfunc Offsetof(x ArbitraryType) uintptrfunc alignof(x ArbitraryType) uintptr
通过分析发现,这三个函数的参数均是ArbitraryType类型,就是接受任何类型的变量。
Sizeof
返回类型 x 所占据的字节数,但不包含 x 所指向的内容的大小。例如,对于一个指针,函数返回的大小为 8 字节(64位机上),一个 slice 的大小则为 slice header 的大小。Offsetof
返回变量指定属性的偏移量,这个函数虽然接收的是任何类型的变量,但是有一个前提,就是变量要是一个struct类型,且还不能直接将这个struct类型的变量当作参数,只能将这个struct类型变量的属性当作参数。alignof
返回变量对齐字节数量2.unsafe包的 *** 作2.1大小Sizeofunsafe.Sizeof函数返回的就是uintptr类型的值,表示所占据的字节数(表达式,即值的大小):
package mainimport ( "fmt" "reflect" "unsafe")func main() { var a int32 var b = &a fmt.Println(reflect.TypeOf(unsafe.Sizeof(a))) // uintptr fmt.Println(unsafe.Sizeof(a)) // 4 fmt.Println(reflect.TypeOf(b).Kind()) // ptr fmt.Println(unsafe.Sizeof(b)) // 8}
对于 a
来说,它是int32
类型,在内存中占4个字节,而对于b
来说,是*int32
类型,即底层为ptr
指针类型,在64位机下占8字节。
对于一个结构体,通过 Offset 函数可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址的内存,就可以达到改变成员值的目的。
这里有一个内存分配相关的事实:结构体会被分配一块连续的内存,结构体的地址也代表了第一个字段的地址。
举个例子:
package mainimport ( "fmt" "unsafe")type user struct { ID int32 name string age byte}func main() { var u = user{ ID: 1,name: "xiaobai",age: 22,} fmt.Println(u) fmt.Println(unsafe.Offsetof(u.ID)) // 0 ID在结构体user中的偏移量,也是结构体的地址 fmt.Println(unsafe.Offsetof(u.name)) // 8 fmt.Println(unsafe.Offsetof(u.age)) // 24 // 根据偏移量修改字段的值 比如将ID字段改为1001 // 因为结构体的地址相当于第一个字段ID的地址 // 直接用unsafe包自带的Pointer获取ID指针 ID := (*int)(unsafe.Pointer(&u)) *ID = 1001 // 更加相对于ID字段的偏移量获取name字段的地址并修改其内容 // 需要用到uintptr进行指针运算 然后再利用unsafe.Pointer这个媒介将uintptr类型转换成一般的指针类型*string name := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&u)) + unsafe.Offsetof(u.name))) *name = "花花" // 同理更改age字段 age := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&u)) + unsafe.Offsetof(u.age))) *age = 33 fmt.Println(u)}
2.3对齐alignof要了解这个函数,你需要了解数据对齐
。简单的说,它让数据结构在内存中以某种的布局存放,是该数据的读取性能能够更加的快速。
cpu 读取内存是一块一块读取的,块的大小可以为 2、4、6、8、16 字节等大小。块大小我们称其为内存访问粒度。
普通字段的对齐值fmt.Printf("bool align: %d\n",unsafe.alignof(bool(true)))fmt.Printf("int32 align: %d\n",unsafe.alignof(int32(0)))fmt.Printf("int8 align: %d\n",unsafe.alignof(int8(0)))fmt.Printf("int64 align: %d\n",unsafe.alignof(int64(0)))fmt.Printf("byte align: %d\n",unsafe.alignof(byte(0)))fmt.Printf("string align: %d\n",unsafe.alignof("EDDYCJY"))fmt.Printf("map align: %d\n",unsafe.alignof(map[string]string{}))
输出结果:
bool align: 1int32 align: 4int8 align: 1int64 align: 8byte align: 1string align: 8map align: 8
在 Go 中可以调用 unsafe.alignof
来返回相应类型的对齐系数。通过观察输出结果,可得知基本都是 2n,最大也不会超过 8
。这是因为我们的64位编译器默认对齐系数是 8,因此最大值不会超过这个数。
#pragma pack(n)
)或当前成员变量类型的长度(unsafe.Sizeof
),取最小值作为当前类型的对齐值。其偏移量必须为对齐值的整数倍结构体本身,对齐值必须为编译器默认对齐长度或结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值结合以上两点,可得知若编译器默认对齐长度超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的
结构体的对齐值下面来看一下结构体的对齐:
type part struct { a bool // 1 b int32 //4 c int8 // 1 d int64 // 8 e byte // 1}func main() { var p part fmt.Println(unsafe.Sizeof(p)) // 32}
按照普通字段(结构体内成员变量)的对齐方式,我们可以计算得出,这个结构体的大小占1+4+1+8+1=15
个字节,但是用unsafe.Sizeof
计算发现part结构体
占32
字节,是不是有点惊讶 总结
以上是内存溢出为你收集整理的Go语言源码分析之unsafe全部内容,希望文章能够帮你解决Go语言源码分析之unsafe所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)