go addressable 详解

原文作者:smallnest

Go语言规范中规定了可寻址(addressable)对象的定义,

For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal. If the evaluation of x would cause a run-time panic, then the evaluation of &x does too.

对于一个对象x, 如果它的类型为T, 那么&x则会产生一个类型为*T的指针,这个指针指向x, 这是这一段的第一句话,也是我们在开发过程中经常使用的一种获取对象指针的一种方式。

addressable

上面规范中的这段话规定, x必须是可寻址的, 也就是说,它只能是以下几种方式:

  • 1、一个变量: &x
  • 2、指针引用(pointer indirection): &*x 3、slice索引操作(不管slice是否可寻址): &s[1] 4、可寻址struct的字段: &point.X 5、可寻址数组的索引操作: &a[0] 6、composite literal类型: &struct{ X int }{1}

下列情况x是不可以寻址的,你不能使用&x取得指针:

1、字符串中的字节:

2、map对象中的元素

3、接口对象的动态值(通过type assertions获得)

4、常数

5、literal值(非composite literal)

6、package 级别的函数

7、方法method (用作函数值)

8、中间值(intermediate value):

  • channel receive operations
  • sub-string operations
  • sub-slice operations
  • 加减乘除等运算符
  • 函数调用
  • 显式类型转换
  • 各种类型的操作 (除了指针引用pointer dereference操作 *x):

Tapir Games在他的文章unaddressable-values中做了很好的整理。

有几个点需要解释下:

-常数为什么不可以寻址?: 如果可以寻址的话,我们可以通过指针修改常数的值,破坏了常数的定义。

-map的元素为什么不可以寻址?:两个原因,如果对象不存在,则返回零值,零值是不可变对象,所以不能寻址,如果对象存在,因为Go中map实现中元素的地址是变化的,这意味着寻址的结果是无意义的。

-为什么slice不管是否可寻址,它的元素读是可以寻址的?:因为slice底层实现了一个数组,它是可以寻址的。

-为什么字符串中的字符/字节又不能寻址呢:因为字符串是不可变的。

规范中还有几处提到了 addressable:

-调用一个receiver为指针类型的方法时,使用一个addressable的值将自动获取这个值的指针++--语句的操作对象必须是addressable或者是map的index操作

-赋值语句=的左边对象必须是addressable,或者是map的index操作,或者是_上条同样使用for ... range语句

reflect.ValueCanAddr方法和CanSet方法

在我们使用reflect执行一些底层的操作的时候, 比如编写序列化库、rpc框架开发、编解码、插件开发等业务的时候,经常会使用到reflect.ValueCanSet方法,用来动态的给对象赋值。 CanSetCanAddr只加了一个限制,就是struct类型的unexported的字段不能Set,所以我们这节主要介绍CanAddr

并不是任意的reflect.ValueCanAddr方法都返回true,根据它的godoc,我们可以知道:

CanAddr reports whether the value's address can be obtained with Addr. Such values are called addressable. A value is addressable if it is an element of a slice, an element of an addressable array, a field of an addressable struct, or the result of dereferencing a pointer. If CanAddr returns false, calling Addr will panic.

也就是只有下面的类型reflect.ValueCanAddr才是true, 这样的值是addressable:

-slice的元素

-可寻址数组的元素

-可寻址struct的字段

-指针引用的结果

与规范中规定的addressable, reflect.Valueaddressable范围有所缩小, 比如对于栈上分配的变量, 随着方法的生命周期的结束, 栈上的对象也就被回收掉了,这个时候如果获取它们的地址,就会出现不一致的结果,甚至安全问题。

对于栈和堆的对象分配以及逃逸分析,你可以看 William Kennedy 写的系列文章: Go 语言机制之逃逸分析

所以如果你想通过reflect.Value对它的值进行更新,应该确保它的CanSet方法返回true,这样才能调用SetXXX进行设置。

使用reflect.Value的时候有时会对func Indirect(v Value) Valuefunc (v Value) Elem() Value两个方法有些迷惑,有时候他们俩会返回同样的值,有时候又不会。

总结一下:

  1. 如果reflect.Value是一个指针, 那么v.Elem()等价于reflect.Indirect(v)
  2. 如果不是指针 2.1 如果是interface, 那么reflect.Indirect(v)返回同样的值,而v.Elem()返回接口的动态的值 2.2 如果是其它值, v.Elem()会panic,而reflect.Indirect(v)返回原值

下面的代码列出一些reflect.Value是否可以addressable, 你需要注意数组和struct字段的情况,也就是x7x9x14x15的正确的处理方式。

 1 package main
 2 import (
 3    "fmt"
 4    "reflect"
 5    "time"
 6 )
 7 func main() {
 8    checkCanAddr()
 9 }
10 type S struct {
11    X int
12    Y string
13    z int
14 }
15 func M() int {
16    return 100
17 }
18 var x0 = 0
19 func checkCanAddr() {
20    // 可寻址的情况
21    v := reflect.ValueOf(x0)
22    fmt.Printf("x0: %v \tcan be addressable and set: %t, %t\n", x0, v.CanAddr(), v.CanSet()) //false,false
23    var x1 = 1
24    v = reflect.Indirect(reflect.ValueOf(x1))
25    fmt.Printf("x1: %v \tcan be addressable and set: %t, %t\n", x1, v.CanAddr(), v.CanSet()) //false,false
26    var x2 = &x1
27    v = reflect.Indirect(reflect.ValueOf(x2))
28    fmt.Printf("x2: %v \tcan be addressable and set: %t, %t\n", x2, v.CanAddr(), v.CanSet()) //true,true
29    var x3 = time.Now()
30    v = reflect.Indirect(reflect.ValueOf(x3))
31    fmt.Printf("x3: %v \tcan be addressable and set: %t, %t\n", x3, v.CanAddr(), v.CanSet()) //false,false
32    var x4 = &x3
33    v = reflect.Indirect(reflect.ValueOf(x4))
34    fmt.Printf("x4: %v \tcan be addressable and set: %t, %t\n", x4, v.CanAddr(), v.CanSet()) // true,true
35    var x5 = []int{1, 2, 3}
36    v = reflect.ValueOf(x5)
37    fmt.Printf("x5: %v \tcan be addressable and set: %t, %t\n", x5, v.CanAddr(), v.CanSet()) // false,false
38    var x6 = []int{1, 2, 3}
39    v = reflect.ValueOf(x6[0])
40    fmt.Printf("x6: %v \tcan be addressable and set: %t, %t\n", x6[0], v.CanAddr(), v.CanSet()) //false,false
41    var x7 = []int{1, 2, 3}
42    v = reflect.ValueOf(x7).Index(0)
43    fmt.Printf("x7: %v \tcan be addressable and set: %t, %t\n", x7[0], v.CanAddr(), v.CanSet()) //true,true
44    v = reflect.ValueOf(&x7[1])
45    fmt.Printf("x7.1: %v \tcan be addressable and set: %t, %t\n", x7[1], v.CanAddr(), v.CanSet()) //true,true
46    var x8 = [3]int{1, 2, 3}
47    v = reflect.ValueOf(x8[0])
48    fmt.Printf("x8: %v \tcan be addressable and set: %t, %t\n", x8[0], v.CanAddr(), v.CanSet()) //false,false
49    // https://groups.google.com/forum/#!topic/golang-nuts/RF9zsX82MWw
50    var x9 = [3]int{1, 2, 3}
51    v = reflect.Indirect(reflect.ValueOf(x9).Index(0))
52    fmt.Printf("x9: %v \tcan be addressable and set: %t, %t\n", x9[0], v.CanAddr(), v.CanSet()) //false,false
53    var x10 = [3]int{1, 2, 3}
54    v = reflect.Indirect(reflect.ValueOf(&x10)).Index(0)
55    fmt.Printf("x9: %v \tcan be addressable and set: %t, %t\n", x10[0], v.CanAddr(), v.CanSet()) //true,true
56    var x11 = S{}
57    v = reflect.ValueOf(x11)
58    fmt.Printf("x11: %v \tcan be addressable and set: %t, %t\n", x11, v.CanAddr(), v.CanSet()) //false,false
59    var x12 = S{}
60    v = reflect.Indirect(reflect.ValueOf(&x12))
61    fmt.Printf("x12: %v \tcan be addressable and set: %t, %t\n", x12, v.CanAddr(), v.CanSet()) //true,true
62    var x13 = S{}
63    v = reflect.ValueOf(x13).FieldByName("X")
64    fmt.Printf("x13: %v \tcan be addressable and set: %t, %t\n", x13, v.CanAddr(), v.CanSet()) //false,false
65    var x14 = S{}
66    v = reflect.Indirect(reflect.ValueOf(&x14)).FieldByName("X")
67    fmt.Printf("x14: %v \tcan be addressable and set: %t, %t\n", x14, v.CanAddr(), v.CanSet()) //true,true
68    var x15 = S{}
69    v = reflect.Indirect(reflect.ValueOf(&x15)).FieldByName("z")
70    fmt.Printf("x15: %v \tcan be addressable and set: %t, %t\n", x15, v.CanAddr(), v.CanSet()) //true,false
71    v = reflect.Indirect(reflect.ValueOf(&S{}))
72    fmt.Printf("x15.1: %v \tcan be addressable and set: %t, %t\n", &S{}, v.CanAddr(), v.CanSet()) //true,true
73    var x16 = M
74    v = reflect.ValueOf(x16)
75    fmt.Printf("x16: %p \tcan be addressable and set: %t, %t\n", x16, v.CanAddr(), v.CanSet()) //false,false
76    var x17 = M
77    v = reflect.Indirect(reflect.ValueOf(&x17))
78    fmt.Printf("x17: %p \tcan be addressable and set: %t, %t\n", x17, v.CanAddr(), v.CanSet()) //true,true
79    var x18 interface{} = &x11
80    v = reflect.ValueOf(x18)
81    fmt.Printf("x18: %v \tcan be addressable and set: %t, %t\n", x18, v.CanAddr(), v.CanSet()) //false,false
82    var x19 interface{} = &x11
83    v = reflect.ValueOf(x19).Elem()
84    fmt.Printf("x19: %v \tcan be addressable and set: %t, %t\n", x19, v.CanAddr(), v.CanSet()) //true,true
85    var x20 = [...]int{1, 2, 3}
86    v = reflect.ValueOf([...]int{1, 2, 3})
87    fmt.Printf("x20: %v \tcan be addressable and set: %t, %t\n", x20, v.CanAddr(), v.CanSet()) //false,false
88 }

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2018-08-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏个人分享

Hive metastore整体代码分析及详解

  从上一篇对Hive metastore表结构的简要分析中,我再根据数据设计的实体对象,再进行整个代码结构的总结。那么我们先打开metadata的目录,其目录...

81930
来自专栏java学习

面试题28( 关于Float,下列说法错误的是?)

关于Float,下列说法错误的是()? A Float是一个类 B Float在java.lang包中 C Float a=1.0是正确的赋值方法 D Flo...

37440
来自专栏牛肉圆粉不加葱

(1) - Range

在上一小节的很多例子中,都用 by 指定了自定义步长,所有的类型都支持指定自定义步长。但并不是所有的类型都有默认步长,只有以下类型具有默认步长:

7510
来自专栏流媒体

resources.arsc解析

示例apk 示例代码 binary view二进制文件查看工具: android 6.0系统源码(网上搜索下载,这里暂不提供资源)

18520
来自专栏calmound

HDU 4628 Pieces(状态压缩+记忆化搜索)

http://acm.hdu.edu.cn/showproblem.php?pid=4628 题意:给个字符窜,每步都可以删除一个字符窜,问最少用多少步可以删除...

35860
来自专栏算法修养

LeetCode 131 Palindrome Partitioning

思路是,先将所有的回文子串都找出来,记录下左右端点。 然后DFS这些子串就可以了。

9610
来自专栏机器学习原理

图数据库neo4j介绍(5)——常用函数常用函数shortestPath 查询最短路径正则collect数据导入

应用理论:6层关系理论:任何两个事物之间的关系都不会超过6层 查询最短路径的必要性 allShortestPaths [*..n] 用于表示获取n层关系

86320
来自专栏积累沉淀

初识HtmlParser

1、概念 网页解析,即程序自动分析网页内容、获取信息,从而进一步处理信息。 htmlparser包提供方便、简洁的处理html文件的方法,它将html页面中...

24250
来自专栏菩提树下的杨过

重温delphi之控制台程序:Hello World!

这二天用c#开发ActiveX时,发现不管怎么弄,c#就是没办法生成ocx的纯正activeX控件,而且还要强迫用户安装巨大的.net framework(我只...

23980
来自专栏算法修养

PAT 甲级 1021 Deepest Root (并查集,树的遍历)

1021. Deepest Root (25) 时间限制 1500 ms 内存限制 65536 kB 代码长度限制 16000 B ...

42170

扫码关注云+社区

领取腾讯云代金券