反射与指针
# 1.context
# 1.1.定义
Go1.7标准库引入context,又称为上下文,包含goroutine的运行状态、环节、现场等信息。context主要用来在goroutine间传递上下文信息,包括取消信号、超时时间、截止时间、k-v等。
一般情况下,协程的关闭会用到
channel+select方式控制,但涉及到多个协程配合及共享一些全局变量、有共同deadline等,如果这些协程可以同时关闭,用channel+select方式会比较麻烦,这时可以通过context实现,即解决goroutine退出通知、元数据传递等功能。func Background() Context // 根节点附带创建子节点的函数 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context1
2
3
4
5
6
7context会在函数间传递,只需要在适当的时间调用cancel函数向goroutines发出取消信号或者调用value函数取出context中的值。对于context的使用,一般会遵守以下原则:- 不要将
context塞到结构体,直接作为函数的第一参数,一般命名为ctx - 不要向函数传递
nil的context,标准库定义了context:todo - 不要把函数参数塞到
context,context存储的是共享数据 - 同一个
context可以被传递到多个goroutine,context是并发安全的
- 不要将
# 1.2.context值查找
type valueCtx struct { Context key, val interface{} } func (c *valueCtx) String() string { return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) } func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15valueCtx直接将Context作为匿名字段,仅管它只实现了两个方法,其他方法继承自父context,但它仍然属于context。func WithValue(parent Context, key, val interface{}) Context { if key == nil { panic("nil key") } if !reflect.TypeOf(key).Comparable() { panic("key is not comparable") } return &valueCtx{parent, key, val} }1
2
3
4
5
6
7
8
9WithValue(...)是创建valueCtx的函数,要求key是可比较的,因为之后需要通过key取出context中的值,可比较是必须的。通过层层传递context,最终会形成一棵树。
和链表有点像,但它的方向是反的,
context指向它的父节点,链表则指向下一节点。通过withValue函数,可以创建层层的valueCtx,存储goroutine间可以共享的变量。取值的过程,本质上是递归查找的过程。func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) }1
2
3
4
5
6它会顺着链表一直向上找,比较当前节点的
key是否为要找的key,顺着context向前一直找到根节点,一般根节点是emptyCtx,返回nil,因此使用Value方法时要判断结果是否为nil。由于查找顺序是向上的,父节点无法获取子节点存储的值,子节点可以获取父节点存储的值。WithValue创建context节点的过程实际上就是创建链表节点的过程,两个节点的key值是可以相等的,但它们是两个不同的context节点。查找的时候,会向上查找到最后一个挂载的context节点,也就是离得比较近的一个父节点context。整体上来说,用WithValue构造的其实是一个低效率的链表。很多情况下,context会被传递到很多子函数、子协程,可能在各种地方塞入k-v对,很容易陷入什么时候传值、什么时候覆盖、什么时候使用的困扰,这也是context.Value最受争议的地方,因此很多人都建议不采用context传值。
# 1.3.context取消
Context是一个接口,定义了4个方法,它们都是幂等的,多次调用同一方法得到结果相同。Done()返回一个只读的channel,可以表示context被取消的信号,channel关闭才能拿到类型零值,通知协程进行收尾工作。Err()返回一个错误,表示channel被关闭的原因。Deadline返回context的截止时间,通过此时间,函数可以决定是否进行剩余工作。Value()获取之前设置的key对应的值。type Context interface { // 当context被取消或者到deadline,返回一个被关闭的channel Done() <-chan struct{} // 在channel Done关闭后,返回context取消原因 Err() error // 返回context是否会被取消以及自动取消时间 Deadline() (deadline time.Time, ok bool) // 获取key对应的value Value(key interface{}) interface{} }1
2
3
4
5
6
7
8
9
10
11
12
13此外,可取消的
context必须实现另一接口——canceler,源码中*cancelCtx和*timerCtx实现了这个接口。取消接口单独定义是因为cancel是建议性的,而非强制性的。另外,cancel操作是级联的,Done()返回一个只读channel,所有相关函数监听此channel,一旦channel关闭基于channel的广播机制,所有监听者都能听到并退出。type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{} }1
2
3
4源码中定义
Context接口后,给出一个空实现。type emptyCtx int func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { return } func (*emptyCtx) Done() <-chan struct{} { return nil } func (*emptyCtx) Err() error { return nil } func (*emptyCtx) Value(key interface{}) interface{} { return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17这个空实现不存值,不会被
cancel,也没有deadline,它被进一步包装成两个变量,通过到处函数对外公开。var ( background = new(emptyCtx) todo = new(emptyCtx) ) func Background() Context { return background } func TODO() Context { return todo }1
2
3
4
5
6
7
8
9
10
11
12
# 1.4.cancelCtx
另一个比较重要的
context是cancelCtx,它实现了canceler接口,将接口Context作为匿名字段。type cancelCtx struct { Context // 保护之后的字段 mu sync.Mutex done chan struct{} children map[canceler]struct{} err error }1
2
3
4
5
6
7
8
9它实现了
Done()方法,调用时初始化c.done,函数返回一个只读channel,一旦关闭立即读出零值。func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { c.done = make(chan struct{}) } d := c.done c.mu.Unlock() return d }1
2
3
4
5
6
7
8
9比较重要的是
cancel()方法,用于关闭channel,递归取消所有子节点,通过取消信号关闭子context。func (c *cancelCtx) cancel(removeFromParent bool, err error) { // 必须要传err if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // 已经被其他协程取消 } // 给err字段赋值 c.err = err // 关闭channel,通知其他协程 if c.done == nil { c.done = closedchan } else { close(c.done) } // 遍历它的所有子节点 for child := range c.children { // 递归地取消所有子节点 child.cancel(false, err) } // 将子节点置空 c.children = nil c.mu.Unlock() if removeFromParent { // 从父节点中移除自己 removeChild(c.Context, c) } }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可取消的
context一般调用WithCancel(...)创建,传入父context作为根节点生成子context。当WithCancel(...)函数返回的CancelFunc被调用或者父节点的CancelFunc被调用,当前context及子context的done channel都会被关闭。var Canceled = errors.New("context canceled") func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { c := newCancelCtx(parent) propagateCancel(parent, &c) // cancel方法第一个入参是true,说明默认将自己从父节点删除,第二个参数则是固定的取消错误类型 return &c, func() { c.cancel(true, Canceled) } } func newCancelCtx(parent Context) cancelCtx { return cancelCtx{Context: parent} }1
2
3
4
5
6
7
8
9
10
11
12另外注意的是,递归调用子节点
cancel()时第一个参数传入是false,因为当前节点取消并从父节点摘除后,由于设置c.children=nil会把子节点一并回收,无需多做摘除动作,避免遍历子节点调用cancel()时,造成同时遍历和删除一个map的情况。func removeChild(parent Context, child canceler) { p, ok := parentCancelCtx(parent) if !ok { return } p.mu.Lock() if p.children != nil { delete(p.children, child) } p.mu.Unlock() }1
2
3
4
5
6
7
8
9
10
11
重点看
propagateCancel(),这个方法会向上寻找可以挂靠的可取消context,这样调用上层cancel()时,会通过层层传递将挂靠的子context一并cancel。func propagateCancel(parent Context, child canceler) { // 父节点是个空节点 if parent.Done() == nil { return // parent is never canceled } // 找到可以取消的父context if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { // 父节点已经被取消,本节点也要取消 child.cancel(false, p.err) } else { // 父节点未取消 if p.children == nil { p.children = make(map[canceler]struct{}) } // "挂到"父节点上 p.children[child] = struct{}{} } p.mu.Unlock() } else { // 如果没有找到可取消的父context,新启动一个协程监控父节点或子节点取消信号 go func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() } }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从程序分支可以发现,
挂靠时会判断else情况,没有向上找到可以取消的父节点,需要启动一个协程监听父节点或当前节点取消,避免无法捕捉父节点取消信号或自身取消信号。另外,从父节点摘除自身时,会再次判断父节点类型决定是否取消。func parentCancelCtx(parent Context) (*cancelCtx, bool) { for { switch c := parent.(type) { case *cancelCtx: return c, true case *timerCtx: return &c.cancelCtx, true case *valueCtx: parent = c.Context default: return nil, false } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.5.timerCtx
timeCtx基于cancelCtx实现,在此基础上新增了time.Timer和一个deadline,Timer会在deadline到来时自动取消context。type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time }1
2
3
4
5
6timerCtx属于cancelCtx的衍生,所以它也可以被取消,它对cancel()重新实现。func (c *timerCtx) cancel(removeFromParent bool, err error) { // 直接调用cancelCtx的取消方法 c.cancelCtx.cancel(false, err) if removeFromParent { // 从父节点中删除子节点 removeChild(c.cancelCtx.Context, c) } c.mu.Lock() if c.timer != nil { // 关掉定时器,在deadline到来时不会再次取消 c.timer.Stop() c.timer = nil } c.mu.Unlock() }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15创建
timerCtx需要调用WithTimeout,该函数直接调用WithDeadline,传入的deadline是当前时间加上timeout的时间,这也表示WithDeadline需要用的是绝对时间。func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { return WithDeadline(parent, time.Now().Add(timeout)) } func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { // 如果父节点context的deadline早于指定时间,直接构建一个可取消的context // 原因是一旦父节点超时,自动调用cancel函数,子节点也会随之取消 // 所以不用单独处理子节点的计时器时间自动调用cancel函数 return WithCancel(parent) } // 构建 timerCtx c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: deadline, } // 挂靠到父节点上 propagateCancel(parent, c) // 计算当前距离deadline的时间 d := time.Until(deadline) if d <= 0 { // 直接取消 c.cancel(true, DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(true, Canceled) } } c.mu.Lock() defer c.mu.Unlock() if c.err == nil { // d时间后,timer会自动调用cancel函数自动取消 c.timer = time.AfterFunc(d, func() { c.cancel(true, DeadlineExceeded) }) } return c, func() { c.cancel(true, Canceled) } }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
37timerCtx创建时子节点依然会挂靠到父节点,一旦父节点取消,会把取消信号向下传递到子节点,子节点随之取消。如果子节点的deadline比父节点晚,那么父节点提前取消会导致子节点的取消,导致子节点的deadline不起作用。最核心的就是timer的定义,保证d间隔后自动调用cancel()函数取消,传入DeadlineExceeded错误。c.timer = time.AfterFunc(d, func() { c.cancel(true, DeadlineExceeded) }) var DeadlineExceeded error = deadlineExceededError{} type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "context deadline exceeded" }1
2
3
4
5
6
7
8
9
# 2.reflect
# 2.1.定义
Go提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时不知道变量的具体类型,这就是反射。反射提供了一种运行期探知对象的类型信息和内存结构的能力。一般情况下,使用反射的场景有两类:- 不能明确接口调用哪个函数,需要根据传入的参数在运行时决定
- 不能明确传入函数的参数类型,需要在运行时处理任意对象
- 一般情况下并不推荐使用反射,一是难以阅读,二是跳过编译期检查,导致错误延迟发现,最后则是对性能的影响,比正常代码运行速度慢一到两个量级。
# 2.2.反射实现
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } var r io.Reader tty, err := os.OpenFile("/Users/qcrao/Desktop/test", os.O_RDWR, 0) if err != nil { return nil, err } r = tty1
2
3
4
5
6
7
8
9
10
11
12
13
14上述程序首先声明
r的类型是io.Reader,这是r的静态类型,此时它的动态类型是nil,动态值也是nil。r=tty赋值后,r的动态类型变成*os.File,动态值则为打开的文件对象,此时r可以表示为<tty,*os.File>。
虽然此时接口的
fun所指向的函数只有Read,但*os.File还包含了Write函数,也就是*os.File还实现了io.Writer接口,因此可以执行断言。var w io.Writer w = r.(io.Writer)1
2这里之所以用断言,是因为
r的静态类型是io.Reader,并没有实现io.Writer接口,断言能否成要看r的动态类型是否符合要求。此时w也可以表示成<tty,*os.File>,仅管和r表示形式一致,但w可调用的函数取决于它的静态类型io.Writer,w的内存形式如下图。
和
r相比,仅仅是func对应的函数由Read变为write。如果将w赋值给接口,由于所有类型都实现了空接口,因此不需要断言就可以直接赋值。var empty interface{} empty = w1
2
从上面过程可以看出,
interface包含三部分信息,_type是类型信息,*data指向实际类型的实际值,itab包含实际类型的信息,包括大小、包路径、绑定在类型上的各种方法。
reflect包里定义了一个接口和结构体,即reflect.Type和reflect.Value,前者提供关于类型相关信息,后者包含_type和data信息。此外,reflect包中提供了两个基础的关于反射的函数获取上述接口和结构体:func TypeOf(i interface{}) Type func ValueOf(i interface{}) Value1
2TypeOf函数用来提取一个接口中值的类型信息,由于入参是空接口,所以实参会转换为interface类型,相应的类型信息、方法集、值信息都存储在interface变量。func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ) }1
2
3
4这里的
emptyInterface和eface是一回事,eface.typ就是相应的动态类型。type emptyInterface struct { typ *rtype word unsafe.Pointer }1
2
3
4至于
toType函数,只是做了一个类型转换。func toType(t *rtype) Type { if t == nil { return nil } return t }1
2
3
4
5
6返回值
Type实际上是一个接口,定义了很多方法,用来获取类型相关的各种信息,*rtype实现了Type接口。type Type interface { // 此类型的变量对齐后所占用的字节数 Align() int // 如果是struct的字段,对齐后占用的字节数 FieldAlign() int // 返回类型方法集里的第`i`(传入的参数)个方法 Method(int) Method // 通过名称获取方法 MethodByName(string) (Method, bool) // 获取类型方法集里导出的方法个数 NumMethod() int // 类型名称 Name() string // 返回类型所在的路径 PkgPath() string // 返回类型的大小,和unsafe.Sizeof功能类似 Size() uintptr // 返回类型的字符串表示形式 String() string // 返回类型的类型值 Kind() Kind // 类型是否实现了接口u Implements(u Type) bool // 是否可以赋值给u AssignableTo(u Type) bool // 是否可以类型转换成u ConvertibleTo(u Type) bool // 类型是否可以比较 Comparable() bool // 下面这些函数只有特定类型可以调用,Key、Elem两个方法就只能是Map类型才能调用 // 类型所占据的位数 Bits() int // 返回通道的方向,只能是chan类型调用 ChanDir() ChanDir // 返回类型是否是可变参数,只能是func类型调用 // 比如t是类型func(x int, y ... float64) // 那么t.IsVariadic() == true IsVariadic() bool // 返回内部子元素类型,只能由类型Array,Chan,Map,Ptr,Slice调用 Elem() Type // 返回结构体类型的第i个字段,只能是结构体类型调用 // 如果i超过了总字段数,就会 panic Field(i int) StructField // 返回嵌套的结构体的字段 FieldByIndex(index []int) StructField // 通过字段名称获取字段 FieldByName(name string) (StructField, bool) // 返回名称符合func函数的字段 FieldByNameFunc(match func(string) bool) (StructField, bool) // 获取函数类型的第i个参数的类型 In(i int) Type // 返回map的key类型,只能由类型map调用 Key() Type // 返回Array的长度,只能由类型Array调用 Len() int // 返回类型字段的数量,只能由类型Struct调用 NumField() int // 返回函数类型的输入参数个数 NumIn() int // 返回函数类型的返回值个数 NumOut() int // 返回函数类型的第i个值的类型 Out(i int) Type // 返回类型结构体的相同部分 common() *rtype // 返回类型结构体的不同部分 uncommon() *uncommonType }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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100common()返回rType类型,它和_type是一回事,两边要保持同步。type rtype struct { size uintptr ptrdata uintptr hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 alg *typeAlg gcdata *byte str nameOff ptrToThis typeOff }1
2
3
4
5
6
7
8
9
10
11
12
13所有的类型都会包含
rType字段,表示各种类型的公共信息,不同类型包含自己的一些独特部分,例如arrayType和chanType都包含rType,前者还包含slice、len等和数组相关的信息,后者包含dir表示通道方向的信息。// arrayType represents a fixed array type. type arrayType struct { rtype `reflect:"array"` elem *rtype // array element type slice *rtype // slice type len uintptr } // chanType represents a channel type. type chanType struct { rtype `reflect:"chan"` elem *rtype // channel element type dir uintptr // channel direction (ChanDir) }1
2
3
4
5
6
7
8
9
10
11
12
13
14Type接口实现了String()函数,满足fmt.Stringer接口,因此使用fmt.Println打印输出的是String()的结果,如果使用%T格式输出参数,输出的是reflect.TypeOf的结果,也就是动态类型。fmt.Printf("%T", 3) // int1再看
ValueOf函数,返回值reflect.Value表示interface里存储的实际变量,它能提供实际变量的各种信息,相关的方法常常结合类型信息和值信息。func ValueOf(i interface{}) Value { if i == nil { return Value{} } // …… return unpackEface(i) } // 分解 eface func unpackEface(i interface{}) Value { e := (*emptyInterface)(unsafe.Pointer(&i)) t := e.typ if t == nil { return Value{} } f := flag(t.Kind()) if ifaceIndir(t) { f |= flagIndir } return Value{t, e.word, f} }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Value结构体定义了很多方法,通过这些方法可以直接操作Value字段ptr所指向的实际数据。// 设置切片的 len 字段,如果类型不是切片,就会panic func (v Value) SetLen(n int) // 设置切片的 cap 字段 func (v Value) SetCap(n int) // 设置字典的 kv func (v Value) SetMapIndex(key, val Value) // 返回切片、字符串、数组的索引 i 处的值 func (v Value) Index(i int) Value // 根据名称获取结构体的内部字段值 func (v Value) FieldByName(name string) Value // 用来获取 int 类型的值 func (v Value) Int() int64 // 用来获取结构体字段(成员)数量 func (v Value) NumField() int // 尝试向通道发送数据(不会阻塞) func (v Value) TrySend(x reflect.Value) bool // 通过参数列表 in 调用 v 值所代表的函数(或方法 func (v Value) Call(in []Value) (r []Value) // 调用变参长度可变的函数 func (v Value) CallSlice(in []Value) []Value1
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通过
Type()方法和Interface()方法可以打通interface、Type、Value三者。Type()方法可以返回变量的类型信息,与reflect.TypeOf()等价,Interface()方法可以将Value恢复成原来的interface。
总的来说,
TypeOf()函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息;ValueOf()函数返回一个结构体变量,包含类型信息以及实际值。
上图中,
rType实现了Type接口,是所有类型的公共部分,emptyInterface结构体和eface其实等同,rType和_type等同,只是一些字段稍有差异。反射三大定律
1.反射是一种检测存储在
interface中的类型和值机制,可以通过TypeOf()函数和ValueOf()函数得到2.
ValueOf的返回值通过Interface()函数可以反向转变为interface变量3.反射变量必须是可设置的,即反射变量存储原变量本身
# 2.3.反射比较对象相同
Go提供了一个通用函数实现对象对比功能,该函数的参数是两个interface,代表支持任意输入类型,输出bool值表示两个入参是否深度相等。当然,如果是不同的类型,即使底层类型相同,相应的值也相同,两个也不是深度相等。func DeepEqual(x, y interface{}) bool type MyInt int type YourInt int func main() { m := MyInt(1) y := YourInt(1) fmt.Println(reflect.DeepEqual(m, y)) // false }1
2
3
4
5
6
7
8
9
10
11一般情况下,
DeepEqual的实现只需要递归调用==,但func、float类型,由于不可比较或精度不同,无法利用DeepEqual比较深度,包含这类参数的struct、interface、array等也不能比较。对于指针类型,两个值相等的指针就是深度相等,无需关心指向内容;对于指向相同slice、map的两个变量也是深度相等,不关心具体内容;有环类型的变量比较时,需要对已比较的内容作标记,避免陷入无限循环。func DeepEqual(x, y interface{}) bool { if x == nil || y == nil { return x == y } v1 := ValueOf(x) v2 := ValueOf(y) if v1.Type() != v2.Type() { return false } return deepValueEqual(v1, v2, make(map[visit]bool), 0) }1
2
3
4
5
6
7
8
9
10
11DeepEqual进行比较时,会获取x、y的反射对象,比较两者的动态类型,动态类型相同的情况下,会调用子函数deepValueEqual,内部通过递归判断对象深度是否相等。// deepValueEqual函数 ... case Map: if v1.IsNil() != v2.IsNil() { return false } if v1.Len() != v2.Len() { return false } if v1.Pointer() == v2.Pointer() { return true } for _, k := range v1.MapKeys() { val1 := v1.MapIndex(k) val2 := v2.MapIndex(k) if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) { return false } } return true ...1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.unsafe
# 3.1.指针和unsafe
相比
C语言中指针的灵活,Go的指针作了很多限制,避免指针带来的危险性,具体限制包括:禁止指针数学运算、禁止指针相互转换、禁止指针比较、禁止指针相互赋值。相比指针,unsafe.Pointer更灵活,可以指向任意类型。type ArbitraryType int type Pointer *ArbitraryType1
2
3unsafe包提供了两点重要能力:任意类型指针和unsafe.Pointer可以相互转换,uintptr类型和unsafe.Pointer可以相互转换。
pointer不能直接进行数学运算,但可以转换成uintptr类型进行数学运算,再转换成pointer类型。// uintptr是一个整数类型,足够大,可以存储 type uintptr uintptr1
2这里的
uintptr没有指针的语义,意思就是uintptr指向的对象会被gc无情回收。但unsafe.Pointer有指针语义,可以保护它所指向的对象有用的情况下不会被垃圾回收。unsafe包中的几个函数都是在编译期间执行完毕,利用了编译器对内存分配的了解。
# 3.2.私有成员修改
针对结构体,通过
offset函数可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址内存,就可以达到改变成员值的目的。type Programmer struct { name string language string } func main() { p := Programmer{"stefno", "go"} fmt.Println(p) name := (*string)(unsafe.Pointer(&p)) *name = "qcrao" lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language))) *lang = "Golang" fmt.Println(p) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17name是结构体的第一个成员,因此可以直接将&p解析成*string,对于结构体的私有成员,现在通过unsafe.Pointer可以改变它们的值。进一步将Programmer结构体升级并放在其他包,通过unsafe.SizeOf()函数获取成员大小,偏移修改结构体私有成员变量。type Programmer struct { name string age int language string } func main() { p := Programmer{"stefno", 18, "go"} fmt.Println(p) lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Sizeof(int(0)) + unsafe.Sizeof(string("")))) *lang = "Golang" fmt.Println(p) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.3.字符串和byte切片转换
一般情况下,字符串和
byte切片转换主要通过遍历和挨个赋值实现,这种方式效率很低,可以看一下slice和string的底层数据结构。type StringHeader struct { Data uintptr Len int } type SliceHeader struct { Data uintptr Len int Cap int }1
2
3
4
5
6
7
8
9
10根据底层定义,
slice和[]byte转换只需要共享底层的Data和Len就可以实现zero-copy,这就涉及到基于unsafe.Pointer指针强转。func string2bytes(s string) []byte { return *(*[]byte)(unsafe.Pointer(&s)) } func bytes2string(b []byte) string{ return *(*string)(unsafe.Pointer(&b)) }1
2
3
4
5
6