unsafe类型转换

Posted by 渐行渐远 on Monday, January 3, 2022 共1242字

reflect.Value 的类型定义,下面是我机器上的代码路径。typ 存储了元素的真实类型, ptr 是元素实际存储的值

/usr/local/Cellar/go@1.13/1.13.12/libexec/src/reflect/value.go

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// To compare two Values, compare the results of the Interface method.
// Using == on two Values does not compare the underlying values
// they represent.
type Value struct {
    // typ holds the type of the value represented by a Value.
    typ *rtype

	// Pointer-valued data or, if flagIndir is set, pointer to data.
	// Valid when either flagIndir is set or typ.pointers() is true.
	ptr unsafe.Pointer

	// flag holds metadata about the value.
	// The lowest bits are flag bits:
	//	- flagStickyRO: obtained via unexported not embedded field, so read-only
	//	- flagEmbedRO: obtained via unexported embedded field, so read-only
	//	- flagIndir: val holds a pointer to the data
	//	- flagAddr: v.CanAddr is true (implies flagIndir)
	//	- flagMethod: v is a method value.
	// The next five bits give the Kind of the value.
	// This repeats typ.Kind() except for method values.
	// The remaining 23+ bits give a method number for method values.
	// If flag.kind() != Func, code can assume that flagMethod is unset.
	// If ifaceIndir(typ), code can assume that flagIndir is set.
	flag

	// A method value represents a curried method invocation
	// like r.Read for some receiver r. The typ+val+flag bits describe
	// the receiver r, but the flag's Kind bits say Func (methods are
	// functions), and the top bits of the flag give the method number
	// in r's type's method table.
}

type flag uintptr

在着重强调一下 rtype 类型,在 Go 语言中,对于任意一种类型都存在一个与之对应的 rtype 类型

/usr/local/Cellar/go@1.13/1.13.12/libexec/src/reflect/type.go

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
    size       uintptr
    ptrdata    uintptr  // number of bytes in the type that can contain pointers
    hash       uint32   // hash of type; avoids computation in hash tables
    tflag      tflag    // extra type information flags
    align      uint8    // alignment of variable with this type
    fieldAlign uint8    // alignment of struct field with this type
    kind       uint8    // enumeration for C
    alg        *typeAlg // algorithm table
    gcdata     *byte    // garbage collection data
    str        nameOff  // string form
    ptrToThis  typeOff  // type for pointer to this type, may be zero
}

上面这些最多也就是帮我们理解,接口类型其实包含实际的值,和实际的类型。下面才是我们核心的输出

结构体占用内存的大小

我们定义一个结构体类型,如何计算结构体类型占内存的大小呢?我们可以通过映射 rtype.size 来获取,这个计算结果是经过内存对齐之后的

 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

type tflag uint8
type nameOff int32
type typeOff int32

type ValueCopy struct {
	typ  *rtype
	ptr  unsafe.Pointer
	flag uintptr
}

// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
	size       uintptr
	ptrdata    uintptr  // number of bytes in the type that can contain pointers
	hash       uint32   // hash of type; avoids computation in hash tables
	tflag      tflag    // extra type information flags
	align      uint8    // alignment of variable with this type
	fieldAlign uint8    // alignment of struct field with this type
	kind       uint8    // enumeration for C
	alg        *typeAlg // algorithm table
	gcdata     *byte    // garbage collection data
	str        nameOff  // string form
	ptrToThis  typeOff  // type for pointer to this type, may be zero
}

// a copy of runtime.typeAlg
type typeAlg struct {
	// function for hashing objects of this type
	// (ptr to object, seed) -> hash
	hash func(unsafe.Pointer, uintptr) uintptr
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
}

type Animal struct {
	Name string
	Age  int
}

func main() {
	animal := Animal{
		Name: "1",
		Age:  2,
	}

	rv := reflect.ValueOf(animal)
	realAnimal := (*ValueCopy)(unsafe.Pointer(&rv))
	fmt.Println(realAnimal.typ.size)
	// output: 24
}

结构体转[]byte切片

如何实现结构体转 byte 切片呢?结合上面的类型,我们可以实现最简单的转换。我们在上述代码的基础上追加了部分调整

首先我们将 []byte 转换为 SliceHeaderCopy 的内存结构,然后给转换后的 sli 进行赋值操作,我们就完成了结构体到 []byte 的转换。[]byte 中 存储的仍然是原 animal 的地址。

 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
type SliceHeaderCopy struct {
	Data unsafe.Pointer
	Len  int
	Cap  int
}

type Animal struct {
	Name string
	Age  int
}

func main() {
	animal := Animal{
		Name: "1",
		Age:  2,
	}

	rv := reflect.ValueOf(animal)
	realAnimal := (*ValueCopy)(unsafe.Pointer(&rv))

	var bytesContent []byte
	sli := (*SliceHeaderCopy)(unsafe.Pointer(&bytesContent))
	fmt.Println(realAnimal.typ.size)
	// output: 24
	
	sli.Data = realAnimal.ptr
	sli.Len = int(realAnimal.typ.size)
	sli.Cap = int(realAnimal.typ.size)
	fmt.Println(bytesContent)

	animalCopy := (*Animal)(unsafe.Pointer(&bytesContent[0]))
	fmt.Println(animalCopy.Name, animalCopy.Age)
}

如果 animal 对象被删除,这个转换就会出现异常。因为 sli 中 Data 执行的地址发生了改变。因为 []byte 中存储的内容是:

[170 4 13 1 0 0 0 0 1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0]

前16位标识的是 Name,后8位标识的是 Age。Name 中的内容是通过地址引用的,所以,当地址失效的话,通过 unsafe.Pointer 转换就会发生异常

String 内容存放到 []byte