unsafe
安全指针
Go 语言的函数传参都是
值传递
package main
import "fmt"
func double(x *int) {
*x += *x
x = nil//x是值拷贝,x和a都指向了一块内存地址,x=nil,并不影响a指向的内存地址中的数据
}
func main() {
var a = 3
double(&a)
fmt.Println(a) // 6
p := &a
double(p)
fmt.Println(a, p == nil) // 12 false
}
限制一:
Go 的指针不能进行数学运算
a := 5
p := &a
p++ //会报编译错误:invalid operation
p = &a + 3
限制二:
不同类型的指针不能相互转换
a := int(100)
var f *float64
f = &a //cannot use &a (type *int) as type *float64 in assignment
限制三:
不同类型的指针不能使用 == 或 != 比较
只有在两个指针类型相同或者可以相互转换的情况下,才可以对两者进行比较。另外,指针可以通过 == 和 != 直接和 nil 作比较
限制四:
不同类型的指针变量不能相互赋值
unsafe包
unsafe.Pointer非类型安全的指针,unsafe 包用于 Go 编译器,在编译阶段使用。
它可以绕过 Go 语言的类型系统,直接操作内存,使代码更高效。
unsafe.Pointer
通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(必须转换到某一类型的普通指针)。
uintpt
用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被回收
type uintptr uintpt
两者关系
unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。
只要将Pointer类型转换成uintptr类型,做完加减法后,转换成Pointer,通过*操作,取值,修改值,随意
unsafe.Pointer 可以让你的变量在不同的普通指针类型转来转去,也就是表示为任意可寻址的指针类型。而 uintptr 常用于与 unsafe.Pointer 打配合,用于做指针运算
unsafe原理
type ArbitraryType int //任意的意思
type Pointer *ArbitraryType //类似于 C 语言里的 void*
unsafe 包还有其他三个函数
func Sizeof(x ArbitraryType) uintptr //返回类型 x 所占据的字节数,但不包含 x 所指向的内容的大小
func Offsetof(x ArbitraryType) uintptr //返回结构体成员在内存中的位置离结构体起始处的字节数,所传参数必须是结构体的成员
func Alignof(x ArbitraryType) uintptr //返回 m,m 是指当类型进行内存对齐时,它分配到的内存地址能整除 m
三个函数都是在编译期间执行,它们的结果可以直接赋给 const 型变量。另外,因为三个函数执行的结果和操作系统、编译器相关,所以是不可移植的
获取slice长度
调用 make 函数新建一个 slice,底层调用的是 makeslice 函数,返回的是 slice 结构体:
func makeslice(et *_type, len, cap int) slice
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
func main() {
sl := make([]int, 10, 20)
l := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&sl)) + uintptr(8)))
fmt.Println(l, len(sl))
c := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&sl)) + uintptr(16)))
fmt.Println(c, cap(sl))
获取map长度
调用make新建一个map,底层调用makemap 函数返回的是 hmap 的指针,注意是指针:
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap
type hmap struct {
count int //元素个数,调用 len(map) 时,直接返回此值
flags uint8
B uint8
noverflow uint16
hash0 uint32
buckets unsafe.Pointer
oldbuckets unsafe.Pointer
nevacuate uintptr
extra *mapextra
}
func main() {
mp := make(map[string]int, 5) //返回的是*hmap 指针
mp["age"] = 18
count := **(**int)(unsafe.Pointer(&mp))
}