golang-标准库(unsafe)
- 下面简单看一个用unsafe对int8类型变量的值进行修改的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package main
import (
"fmt"
"unsafe"
)
func main() {
var a int8 = 1 // 声明一个变量
fmt.Println(a)
// 先将a的指针转为Pointer类型,再将Pointer类型转为*int8指针
i := (*int8)(unsafe.Pointer(&a))
*i = 100 // 对转换来的新指针变量进行赋值操作,改变a的值
fmt.Println(a)
}
- 接下来看unsafe包的另外三个函数:
下面看这三个函数的应用,还是通过一个例子:先看一下包结构,主目录下一个p包,包下面含有一个v.go文件。还有一个main文件
v.go文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package p
import (
"fmt"
)
// 声明一个结构体V,其中的三个属性都是私有的对外部包不可访问
type V struct {
i int32
j int64
k byte
}
// 定义一个打印方法,打印成员变量
func (v *V) PrintInfo() {
fmt.Printf("i=%d, j=%d, k=%d \n",v.i,v.j, v.k)
}
// 定义一个打印变量地址的方法
func (v *V) PrintAddress() {
fmt.Printf("i=%p, j=%p, k=%p \n",&v.i,&v.j, &v.k)
}main.go文件内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44package main
import (
"fmt"
"point/p"
"unsafe"
)
func main() {
v := new(p.V) // 通过new声明一个p.V的类型指针
fmt.Println(unsafe.Sizeof(*v)) // v 对应的结构体在go中占用的内存大小,为24字节
/**
上面为什么会是24字节呢,int32大小为4,int64大小为8,byte大小为1,加起来是13,这就是发生了内存对齐的原因。
下面简单详述一下go中的内存对齐:
1.对于具体类型来说,对齐值=min(编译器默认对齐值,类型大小Sizeof长度)。也就是在默认设置的对齐值和类型的内存占用大小之间,取最小值为该类型的对齐值。简单说就是这个类型的偏移量为它的对齐值的倍数关系。
2.struct在每个字段都内存对齐之后,其本身也要进行对齐,对齐值=min(默认对齐值,字段最大类型长度)。这条也很好理解,struct的所有字段中,最大的那个类型的长度以及默认对齐值之间,取最小的那个。简单说就是结构体整体的大小为它最大类型长度的倍数。
我们再来解释为什么*v占用的大小为24字节,第一个i占用4字节,它是结构体第一个,不需要对齐。
第二个j占用8字节,它的对齐值通过 unsafe.Alignof(int64(0)) 函数得出也为8个字节,应用上面的第一条规则,前面只有四个字节的偏移量,4不是8的倍数,
所以在i的后面需要填充4个字节:iiii----|jjjjjjjj。-为填充,一般是填充0。到这里整体就已经用了16个字节了。
第三个k占用1个字节,它的对齐值通过 unsafe.Alignof(byte(0)) 函数得出也为1个字节。应用上面的第一条规则,16是1的倍数。不需要填充了。
到这里v的字节占用变成了 iiii----|jjjjjjjj|k。接下里应用上面的第二条规则,17不是最大类型int64长度8的倍数,所以还需要填充。
iiii----|jjjjjjjj|k------- ,在后面再填充7个字节,达到24字节后,就满足上面的第二条规则了。至此就是v为何占用24字节。
*/
// 操作第一个私有变量 i
// 因为在go中结构体的指针就对应是第一个成员变量的指针,所以可以将 v 转换为第一个成员变量 i 的指针
i := (*int32)(unsafe.Pointer(v))
*i = 100 // 对私有变量 i 进行赋值操作
v.PrintInfo() // i=:100, j=0, k=0
// 操作第二个私有变量 j
// j到i偏移了8个字节
j := (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int64(0)))))
*j = 200
v.PrintInfo() // i=:100, j=200, k=0
// 操作第三个私有变量 k
// k到j偏移了8个字节
k := (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int64(0))) + uintptr(unsafe.Sizeof(int64(0)))))
*k = 97
v.PrintInfo() // i=:100, j=200, k=97
v.PrintAddress() // i=0xc000016180, j=0xc000016188, k=0xc000016190。可以看到三个变量的地址偏移量完全符合上面的内存对齐规则。(注意内存地址是16进制的)
}
- 下面介绍unsafe包的一个特别用处:unsafe包可以转换成员属性类型相同,但名字不同的结构体,这是go的类型转换不能做到的。同样看例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package main
import (
"fmt"
"unsafe"
)
// 声明两个结构体,它们的成员数据类型都相同,成员名不同。
type T struct{
a int8
b string
}
type K struct {
c int8
d string
}
func main() {
t := &T{100, "hello"}
k := (*K)(unsafe.Pointer(t))
fmt.Println(k) // &{100 hello} , 成功的把T类型变量转换为了K类型
t.a = 99
fmt.Println(k) // &{100 hello}, 改变t,对应k的值也变了。
}
golang-标准库(unsafe)