go addressable 详解

发布于 2023-12-04 09:16:33 字数 11490 浏览 36 评论 0

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 必须是可寻址的, 也就是说,它只能是以下几种方式:

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

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

  • 字符串中的字节
  • map 对象中的元素
  • 接口对象的动态值(通过 type assertions 获得)
  • 常数
  • literal 值(非 composite literal)
  • package 级别的函数
  • 方法 method (用作函数值)
  • 中间值(intermediate value):
    • 函数调用
    • 显式类型转换
    • 各种类型的操作 (除了指针引用 pointer dereference 操作 *x ):
      • channel receive operations
      • sub-string operations
      • sub-slice operations
      • 加减乘除等运算符

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 的正确的处理方式。

package main

import (
  "fmt"
  "reflect"
  "time"
)

func main() {
  checkCanAddr()
}

type S struct {
  X int
  Y string
  z int
}

func M() int {
  return 100
}

var x0 = 0

func checkCanAddr() {
  // 可寻址的情况
  v := reflect.ValueOf(x0)
  fmt.Printf("x0: %v \tcan be addressable and set: %t, %t\n", x0, v.CanAddr(), v.CanSet()) //false,false

  var x1 = 1
  v = reflect.Indirect(reflect.ValueOf(x1))
  fmt.Printf("x1: %v \tcan be addressable and set: %t, %t\n", x1, v.CanAddr(), v.CanSet()) //false,false

  var x2 = &x1
  v = reflect.Indirect(reflect.ValueOf(x2))
  fmt.Printf("x2: %v \tcan be addressable and set: %t, %t\n", x2, v.CanAddr(), v.CanSet()) //true,true

  var x3 = time.Now()
  v = reflect.Indirect(reflect.ValueOf(x3))
  fmt.Printf("x3: %v \tcan be addressable and set: %t, %t\n", x3, v.CanAddr(), v.CanSet()) //false,false

  var x4 = &x3
  v = reflect.Indirect(reflect.ValueOf(x4))
  fmt.Printf("x4: %v \tcan be addressable and set: %t, %t\n", x4, v.CanAddr(), v.CanSet()) // true,true

  var x5 = []int{1, 2, 3}
  v = reflect.ValueOf(x5)
  fmt.Printf("x5: %v \tcan be addressable and set: %t, %t\n", x5, v.CanAddr(), v.CanSet()) // false,false

  var x6 = []int{1, 2, 3}
  v = reflect.ValueOf(x6[0])
  fmt.Printf("x6: %v \tcan be addressable and set: %t, %t\n", x6[0], v.CanAddr(), v.CanSet()) //false,false

  var x7 = []int{1, 2, 3}
  v = reflect.ValueOf(x7).Index(0)
  fmt.Printf("x7: %v \tcan be addressable and set: %t, %t\n", x7[0], v.CanAddr(), v.CanSet()) //true,true

  v = reflect.ValueOf(&x7[1])
  fmt.Printf("x7.1: %v \tcan be addressable and set: %t, %t\n", x7[1], v.CanAddr(), v.CanSet()) //true,true

  var x8 = [3]int{1, 2, 3}
  v = reflect.ValueOf(x8[0])
  fmt.Printf("x8: %v \tcan be addressable and set: %t, %t\n", x8[0], v.CanAddr(), v.CanSet()) //false,false

  //    https://groups.google.com/forum/#!topic/golang-nuts/RF9zsX82MWw   
  var x9 = [3]int{1, 2, 3}
  v = reflect.Indirect(reflect.ValueOf(x9).Index(0))
  fmt.Printf("x9: %v \tcan be addressable and set: %t, %t\n", x9[0], v.CanAddr(), v.CanSet()) //false,false

  var x10 = [3]int{1, 2, 3}
  v = reflect.Indirect(reflect.ValueOf(&x10)).Index(0)
  fmt.Printf("x9: %v \tcan be addressable and set: %t, %t\n", x10[0], v.CanAddr(), v.CanSet()) //true,true

  var x11 = S{}
  v = reflect.ValueOf(x11)
  fmt.Printf("x11: %v \tcan be addressable and set: %t, %t\n", x11, v.CanAddr(), v.CanSet()) //false,false

  var x12 = S{}
  v = reflect.Indirect(reflect.ValueOf(&x12))
  fmt.Printf("x12: %v \tcan be addressable and set: %t, %t\n", x12, v.CanAddr(), v.CanSet()) //true,true

  var x13 = S{}
  v = reflect.ValueOf(x13).FieldByName("X")
  fmt.Printf("x13: %v \tcan be addressable and set: %t, %t\n", x13, v.CanAddr(), v.CanSet()) //false,false

  var x14 = S{}
  v = reflect.Indirect(reflect.ValueOf(&x14)).FieldByName("X")
  fmt.Printf("x14: %v \tcan be addressable and set: %t, %t\n", x14, v.CanAddr(), v.CanSet()) //true,true

  var x15 = S{}
  v = reflect.Indirect(reflect.ValueOf(&x15)).FieldByName("z")
  fmt.Printf("x15: %v \tcan be addressable and set: %t, %t\n", x15, v.CanAddr(), v.CanSet()) //true,false

  v = reflect.Indirect(reflect.ValueOf(&S{}))
  fmt.Printf("x15.1: %v \tcan be addressable and set: %t, %t\n", &S{}, v.CanAddr(), v.CanSet()) //true,true

  var x16 = M
  v = reflect.ValueOf(x16)
  fmt.Printf("x16: %p \tcan be addressable and set: %t, %t\n", x16, v.CanAddr(), v.CanSet()) //false,false

  var x17 = M
  v = reflect.Indirect(reflect.ValueOf(&x17))
  fmt.Printf("x17: %p \tcan be addressable and set: %t, %t\n", x17, v.CanAddr(), v.CanSet()) //true,true

  var x18 interface{} = &x11
  v = reflect.ValueOf(x18)
  fmt.Printf("x18: %v \tcan be addressable and set: %t, %t\n", x18, v.CanAddr(), v.CanSet()) //false,false

  var x19 interface{} = &x11
  v = reflect.ValueOf(x19).Elem()
  fmt.Printf("x19: %v \tcan be addressable and set: %t, %t\n", x19, v.CanAddr(), v.CanSet()) //true,true

  var x20 = [...]int{1, 2, 3}
  v = reflect.ValueOf([...]int{1, 2, 3})
  fmt.Printf("x20: %v \tcan be addressable and set: %t, %t\n", x20, v.CanAddr(), v.CanSet()) //false,false
}

参考资料

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

初见终念

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

qq_E2Iff7

文章 0 评论 0

Archangel

文章 0 评论 0

freedog

文章 0 评论 0

Hunk

文章 0 评论 0

18819270189

文章 0 评论 0

wenkai

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文