go addressable 詳解

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

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的正確的處理方式。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
package mainimport ("fmt""reflect""time")func main() {checkCanAddr()}type S struct {X intY stringz int}func M() int {return 100}var x0 = 0func checkCanAddr() {// 可定址的情況v := reflect.ValueOf(x0)fmt.Printf("x0: %v \tcan be addressable and set: %t, %t\n", x0, v.CanAddr(), v.CanSet()) //false,falsevar x1 = 1v = reflect.Indirect(reflect.ValueOf(x1))fmt.Printf("x1: %v \tcan be addressable and set: %t, %t\n", x1, v.CanAddr(), v.CanSet()) //false,falsevar x2 = &x1v = reflect.Indirect(reflect.ValueOf(x2))fmt.Printf("x2: %v \tcan be addressable and set: %t, %t\n", x2, v.CanAddr(), v.CanSet()) //true,truevar 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,falsevar x4 = &x3v = reflect.Indirect(reflect.ValueOf(x4))fmt.Printf("x4: %v \tcan be addressable and set: %t, %t\n", x4, v.CanAddr(), v.CanSet()) // true,truevar 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,falsevar 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,falsevar 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,truev = reflect.ValueOf(&x7[1])fmt.Printf("x7.1: %v \tcan be addressable and set: %t, %t\n", x7[1], v.CanAddr(), v.CanSet()) //true,truevar 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/RF9zsX82MWwvar 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,falsevar 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,truevar x11 = S{}v = reflect.ValueOf(x11)fmt.Printf("x11: %v \tcan be addressable and set: %t, %t\n", x11, v.CanAddr(), v.CanSet()) //false,falsevar 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,truevar 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,falsevar 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,truevar 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,falsev = reflect.Indirect(reflect.ValueOf(&S{}))fmt.Printf("x15.1: %v \tcan be addressable and set: %t, %t\n", &S{}, v.CanAddr(), v.CanSet()) //true,truevar x16 = Mv = reflect.ValueOf(x16)fmt.Printf("x16: %p \tcan be addressable and set: %t, %t\n", x16, v.CanAddr(), v.CanSet()) //false,falsevar x17 = Mv = reflect.Indirect(reflect.ValueOf(&x17))fmt.Printf("x17: %p \tcan be addressable and set: %t, %t\n", x17, v.CanAddr(), v.CanSet()) //true,truevar x18 interface{} = &x11v = reflect.ValueOf(x18)fmt.Printf("x18: %v \tcan be addressable and set: %t, %t\n", x18, v.CanAddr(), v.CanSet()) //false,falsevar x19 interface{} = &x11v = reflect.ValueOf(x19).Elem()fmt.Printf("x19: %v \tcan be addressable and set: %t, %t\n", x19, v.CanAddr(), v.CanSet()) //true,truevar 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}

參考資料

  • http://www.tapirgames.com/blog/golang-unofficial-faq#unaddressable-values
  • https://blog.golang.org/laws-of-reflection
  • https://stackoverflow.com/questions/25384640/why-golang-reflect-makeslice-returns-un-addressable-value
  • http://grokbase.com/t/gg/golang-nuts/13bwpzedxh/go-nuts-golang-reflect-addressable-is-not-consistency
  • https://github.com/golang/go/issues/11865
  • https://stackoverflow.com/questions/20224478/dereferencing-a-map-index-in-golang
  • https://stackoverflow.com/questions/38168329/why-are-map-values-not-addressable
  • https://stackoverflow.com/questions/40793289/go-struct-literals-why-is-this-one-addressable
  • https://groups.google.com/forum/#!topic/golang-nuts/FnTCX9R_PbM
  • https://stackoverflow.com/questions/24318389/golang-elem-vs-indirect-in-the-reflect-package

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.