go反射原理


go反射原理

本文基於go1.13.15

1.go匯編

1.1 基本語法

go采用plan9的匯編器完成匯編,有下面幾個重要的偽寄存器

  • FP: Frame pointer: 局部變量訪問

  • PC: Program counter: 程序計數器

  • SB: Static base pointer: 全局變量訪問

  • SP: Stack pointer: 存儲棧頂指針

常用指令如下

// 加括號代表是指針的引用 
MOVQ (AX), BX   // => BX = *AX 將AX指向的內存區域8byte賦值給BX 
MOVQ 16(AX), BX // => BX = *(AX + 16) 
// 不加括號是值的引用 
MOVQ AX, BX     // => BX = AX 將AX中存儲的內容賦值給BX,注意區別

LEAQ 96(SP), BP // => BP = (SP+96)
LEAQ go.string."sj"(SB), CX // => CX = &("sj")

JEQ xxx //如果前面的判斷相等則跳轉到xxx
JNQ xxx //如果前面的判斷不相等則跳轉到xxx

CMPQ a,b //比較a和b

SUBQ $104, SP // SP -= 104
ADDQ $104, SP // SP += 104

1.2 生成匯編

  • 鏈接前的代碼

    go tool compile -S -N -l test1.go > result.txt

    • -S

      輸出匯編結果

    • -N

      禁止編譯器優化

    • -l

      禁用內聯編譯

使用go tool complie --help得到說明

  • 鏈接后的代碼
    • go tool compile -N -l test.go
    • go tool objdump test.o

2.Java反射

以class對象為橋梁

2.1 內存分布

2.2 類加載

加載->鏈接->初始化

在加載階段,主要進行如下步驟

  • 通過一個類的全限定名獲取定義此類的二進制字節流
  • 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
  • 在堆中生成一個代表這個類的java.lang.Class對象,引用存放在棧,作為訪問方法區中這個類的各種數據的入口

使用new創建一個對象時,

  • 方法區

會在方法區中創建一個instanceKlass,其中包含了類型的元信息,如方法表、常量池等

在堆區創建instanceOopDesc對象,其中包含instanceKlass對象的引用

存放instanceOopDesc對象的引用

Java的反射即以class對象為入口,訪問類的****元信息

3.go接口

實現go中的接口不需要顯式聲明,只需要實現接口中的全部方法,且實現的方法簽名和接口中的完全一致即可

3.1 接收者

結構體實現接口 結構體指針實現接口
接收者為變量 T T
接收者為指針 T F

上表的解釋

  • 接收者是指針類型的方法,很可能在方法中會對接收者的屬性進行更改操作,從而影響接收者;而對於接收者是值類型的方法,在方法中不會對接收者本身產生影響
  • 如果方法的接收者是值類型,無論調用者是對象還是對象指針,修改的都是對象的副本(對於接收者為指針來說,會先解引用再拷貝對象),不影響調用者;如果方法的接收者是指針類型,則調用者修改的是指針指向的對象本身(對於接收者為變量來說,會先取址再拷貝指針)

3.2 運行時類型

go是強類型的語言,在編譯期會進行類型檢查,在編譯后會對代碼中出現的每一種引用類型創建相應的類型結構體變量用於存儲****元信息

3.2.1 引用類型的運行時類型(runtime/type.go)

type _type struct {
   size       uintptr    //類型大小
   ptrdata    uintptr // size of memory prefix holding all pointers
   hash       uint32    //類型的hash
   tflag      tflag    //類型的flag,用於判斷內部成員訪問權限
   align      uint8    //對齊
   fieldalign uint8    //對齊
   kind       uint8    //類型的種類
   alg        *typeAlg    //類型使用的<hash,equal>函數對
   gcdata    *byte    //gc
   str       nameOff  //類型名稱的字符串偏移量
   ptrToThis typeOff  
}
  • Interfacetype
type interfacetype struct {
   typ     _type    //接口類型
   pkgpath name    //接口包名
   mhdr    []imethod    //接口中的方法表
}
  • structtype
type structtype struct {
   typ     _type
   pkgPath name
   fields  []structfield
}

type structfield struct {
   name       name
   typ        *_type
   offsetAnon uintptr
}
  • ptrtype、functype、slicetype、chantype、arraytype、maptype、uncommontype

uncommontype是指向一個函數指針的數組,收集了這類型的實現的所有方法

3.2.2 eface(runtime/runtime2.go)

表示沒有方法的接口,如interface{}

type eface struct {
   _type *_type    //類型
   data  unsafe.Pointer    //值指針
}

3.2.3 iface(runtime/runtime2.go)

表示有方法的接口

type iface struct {
   tab  *itab    //類型
   data unsafe.Pointer    //值指針
}
type itab struct {
   inter *interfacetype    //接口類型
   _type *_type    //實際類型
   hash  uint32     //_type的hash值拷貝,斷言使用
   _     [4]byte    //對其填充8個字節使用
   fun   [1]uintptr // 接口中的方法調用入口,若有多個方法順序向后排列在內存中
}
img

圖中interfacetype和itab指向的是不同的_type對象

3.3 接口的構造

3.3.1 示例代碼

type myInterface interface {
   test1(id int64) int64
   test2(flag bool)
}

type MyStruct struct {
   Id   int64
   Name string
}

func (m *MyStruct) test1(id int64) int64 {
   return id + m.Id
}

func (m *MyStruct) test2(flag bool) {
   fmt.Println(flag)
}

3.3.2 MyStruct

type."".MyStruct SRODATA size=144

          |--------- size -----| |-------ptrdata-------|
   0x0000 18 00 00 00 00 00 00 00  10 00 00 00 00 00 00 00  ................

         |- hash --||--------|  |------- alg ---------|
   0x0010 dc a7 2c a3 07 08 08 19  00 00 00 00 00 00 00 00  ..,.............

         |------- gcdata ------| |-- str --||ptrToThis|
   0x0020 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

         |------- pkgPath -----| |------- name --------|
   0x0030 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................

         |------- typ ---------| |---- offsetAnon -----|
   0x0040 02 00 00 00 00 00 00 00  02 00 00 00 00 00 00 00  ................

         |------- name --------| |------- typ ---------|
   0x0050 00 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  ........@.......

         |---- offsetAnon -----|
   0x0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
   0x0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
   0x0080 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................

   rel 24+8 t=1 type..alg."".MyStruct+0;定義了使用的<hash,equal>函數對
   rel 32+8 t=1 runtime.gcbits.02+0;gc使用
   rel 40+4 t=5 type..namedata.*main.MyStruct.+0;結構體名稱
   rel 44+4 t=5 type.*"".MyStruct+0;*MyStruct類型
   rel 56+8 t=1 type."".MyStruct+96;
   rel 80+4 t=5 type..importpath."".+0
   rel 96+8 t=1 type..namedata.Id.+0
   rel 104+8 t=1 type.int64+0
   rel 120+8 t=1 type..namedata.Name.+0
   rel 128+8 t=1 type.string+0
   
type..namedata.*main.MyStruct. SRODATA dupok size=17
   0x0000 01 00 0e 2a 6d 61 69 6e 2e 4d 79 53 74 72 75 63  ...*main.MyStruc
   0x0010 74  

type..namedata.Id. SRODATA dupok size=5
   0x0000 01 00 02 49 64                                   ...Id

type..namedata.Name. SRODATA dupok size=7
   0x0000 01 00 04 4e 61 6d 65                             ...Name

3.3.3 myInterface

go.itab.*"".MyStruct,"".myInterface SRODATA dupok size=40

          |--------- inter -----|  |--------- _type -----|
   0x0000  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  ................

         |- hash --| |-- 對齊 --| | fun[0]  | | fun[1]  |
   0x0010 58 6c 00 8b  00 00 00 00   00 00 00 00  00 00 00 00  Xl..............

   0x0020 00 00 00 00 00 00 00 00                          ........

   rel 0+8 t=1 type."".myInterface+0
   rel 8+8 t=1 type.*"".MyStruct+0
   rel 24+8 t=1 "".(*MyStruct).test1+0
   rel 32+8 t=1 "".(*MyStruct).test2+0

3.3.4 類型轉換

runtime/iface.go中給出了許多進行類型轉換的接口,go編譯器只在必要時(如強制類型轉換)才調用下面接口

  • convT2E
  • convT2I
  • convT16
  • convTstring
  • convTslice
  • ...

4.go反射

以空接口為橋梁

4.1 TypeValue

4.1.1 TypertypeemptyInterface

1)Type

是一個接口,提供了許多用於訪問類型元信息的方法

type Type interface {
        // 此類型的變量對齊后所占用的字節數
        Align() int
        
        // 如果是 struct 的字段,對齊后占用的字節數
        FieldAlign() int
        
        // 返回類型方法集里的第 `i` (傳入的參數)個方法
        Method(int) Method
        
        // 通過名稱獲取方法
        MethodByName(string) (Method, bool)
        
        // 獲取類型方法集里導出的方法個數
        NumMethod() int
        
        // 類型名稱
        Name() string
        
        // 返回類型所在的路徑,如:encoding/base64
        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, or Slice 調用
        Elem() Type
        
        // 返回結構體類型的第 i 個字段,只能是結構體類型調用
        // 如果 i 超過了總字段數,就會 panic
        Field(i int) StructField
        
        // 返回嵌套的結構體的字段
        FieldByIndex(index []int) StructField
        
        // 通過字段名稱獲取字段
        FieldByName(name string) (StructField, bool)
        
        // 傳入一個含有判斷邏輯的函數,返回名稱符合函數內判斷邏輯的字段
        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
}
2)rtype

結構類,是Type的實現,其元素和runtime下的_type完全一致,目的是為了反射期間對運行時類型變量進行成功轉型

3)emptyInterface
type emptyInterface struct {
   typ  *rtype
   word unsafe.Pointer
}

此接口和runtime下的eface完全一致,目的是為了反射期間可以將eface成功轉型

4)TypeOf函數

這里會用到unsafe.Pointer來完成成功的指針轉型操作,零拷貝

func TypeOf(i interface{}) Type {
   eface := *(*emptyInterface)(unsafe.Pointer(&i))
   return toType(eface.typ)
}

func toType(t *rtype) Type {
   if t == nil {
      return nil
   }
   return t
}

調用此方法經過下面步驟

  • 轉型為eface
  • 轉型為emptyInterface
  • 取出實際類型rtype

得到rtype后,后續就可以通過調用Type提供的方法來反射獲取類型的元信息

5)反射期和運行期的類型對比
反射(reflect包下) 運行時(runtime包下)
rtype _type
emptyInterface eface
xxxType xxxtype

xxx包括map、chan、interface、uncommon、array、func、ptr、slice、struct

4.1.2 Value

1)Value
type Value struct {
   //類型
   typ *rtype
   //值
   ptr unsafe.Pointer
   //標志位,不同位代表不同標志,包含是否可尋址、變量是否可導出等等
   flag
}

Value結構體很簡單,由類型、原變量指針和標志位組成,其定義了許多方法,如

  • 直接修改原變量值的方法
 // 設置切片的 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
  • 判斷可訪問性的方法(圍繞flag展開)
//可尋址
func (v Value) CanAddr() bool {
   return v.flag&flagAddr != 0
}

//可賦值
func (v Value) CanSet() bool {
   return v.flag&(flagAddr|flagRO) == flagAddr
}
  • .......
2)Header

“Go 語言里的引用類型有如下幾個:切片、映射、通道、接口和函數類型。當聲明上述類型的變量時,創建的變量被稱作標頭(header)值。從技術細節上說,字符串也是一種引用類型

每個引用類型創建的標頭值是包含一個指向底層數據結構的指針。每個引用類型還包含一組獨特的字段,用於管理底層數據結構。因為標頭值是為復制而設計的,所以永遠不需要共享一個引用類型的值。”

——《go語言實戰》

go語言中提供了StringHeader和SliceHeader來反射修改屬性,其余引用類型使用Value內置的方法進行操作

3)ValueOf方法
func ValueOf(i interface{}) Value {
   if i == nil {
      return Value{}
   }
   escapes(i)
   return unpackEface(i)
}



func unpackEface(i interface{}) Value {
   e := (*emptyInterface)(unsafe.Pointer(&i))
   // NOTE: don't read e.word until we know whether it is really a pointer or not.
   t := e.typ
   if t == nil {
      return Value{}
   }
   f := flag(t.Kind())
   if ifaceIndir(t) {
      f |= flagIndir
   }
   return Value{t, e.word, f}
}

經歷如下步驟

  • 將變量轉型為eface
  • 將eface轉型為emptyInterface
  • 將實際類型、原變量指針(如果傳入的是指針,如果傳入的變量,獲取到的是副本的指針),flag三者作為參數構建Value

4.2 常見誤區

4.2.1 接口為nil

var buf *bytes.Buffer
//buf = new(bytes.Buffer) //賦值
var out io.Writer = buf
if out != nil {
   out.Write([]byte("done!\n")) //buf賦值刪掉時panic,因為type不為nil,value為nil
}

4.2.2 value不可修改原值

1)傳入變量
func main(){
   //指針直接構成eface
   //變量需要調用convT2E構造eface
   var myStruct = MyStruct{
      Id:   1,
      Name: "sj",
   }
   val := reflect.ValueOf(myInter)
   fmt.Println(val)
}

在myStruct傳入的值為

  • MyStruct變量

會觸發runtime.convT2E

func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
   if raceenabled {
      raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
   }
   if msanenabled {
      msanread(elem, t.size)
   }
   x := mallocgc(t.size, t, true)typedmemmove(t, x, elem)
   e._type = t
   e.data = x
   return
}

這里可以看出,最終返回的eface中的data是堆上的副本的指針,不是原變量的指針

  • MyStruct變量指針

直接將傳入的指針作為eface的data域,沒有觸發拷貝

從上可知,在調用ValueOf方法獲取Value對象時,如果沒有傳入指針,則不可以正確的修改到原變量的值,因此go的CanAddr方法在這種情況下也會返回false

2)元素未導出

利用反射機制,對於結構體中未導出成員,可以讀取,但不能修改其值。這種情況下CanSet方法返回false

4.3 示例代碼

此示例包括了接口的構造過程、類型轉換以及反射調用點

func main(){
   var myInter myInterface
   myInter = &MyStruct{
      Id:   1,
      Name: "sj",
   }
   reflect.ValueOf(myInter)
}
"".main STEXT size=114 args=0x0 locals=0x30
   0x0000 00000 (test1.go:26) TEXT   "".main(SB), ABIInternal, $48-0
   //判斷是否要擴展棧
   0x0000 00000 (test1.go:26) MOVQ   (TLS), CX
   0x0009 00009 (test1.go:26) CMPQ   SP, 16(CX)
   0x000d 00013 (test1.go:26) JLS    107
   //移動SP指針,分配出48字節的棧空間
   0x000f 00015 (test1.go:26) SUBQ   $48, SP
   //存儲調用點到棧底8字節
   0x0013 00019 (test1.go:26) MOVQ   BP, 40(SP)
   //BP中存儲棧底地址
   0x0018 00024 (test1.go:26) LEAQ   40(SP), BP
   //一些初始化動作
   0x001d 00029 (test1.go:26) FUNCDATA   $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
   0x001d 00029 (test1.go:26) FUNCDATA   $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
   0x001d 00029 (test1.go:26) FUNCDATA   $2, gclocals·bfec7e55b3f043d1941c093912808913(SB)
   0x001d 00029 (test1.go:30) PCDATA $0, $1
   0x001d 00029 (test1.go:30) PCDATA $1, $0
   //將type."".MyStruct的指針傳入AX
   0x001d 00029 (test1.go:30) LEAQ   type."".MyStruct(SB), AX
   0x0024 00036 (test1.go:30) PCDATA $0, $0
   //將type."".MyStruct的指針移入棧頂
   0x0024 00036 (test1.go:30) MOVQ   AX, (SP)
   //傳入棧頂的type."".MyStruct的指針,調用newobject,其會在堆上申請一塊大小相當的內存,
   //並返回一個*MyStruct,放入SP的8~15字節
   0x0028 00040 (test1.go:30) CALL   runtime.newobject(SB)
   0x002d 00045 (test1.go:30) PCDATA $0, $1
   //將*MyStruct傳入AX
   0x002d 00045 (test1.go:30) MOVQ   8(SP), AX
   //將1賦給*MyStruct指向的堆中的Id字段
   0x0032 00050 (test1.go:29) MOVQ   $1, (AX)
   //將2賦給*MyStruct指向的堆中的StringHeader的Len
   0x0039 00057 (test1.go:30) MOVQ   $2, 16(AX)
   0x0041 00065 (test1.go:30) PCDATA $0, $2
   //將go.string."sj"的地址傳入CX
   0x0041 00065 (test1.go:30) LEAQ   go.string."sj"(SB), CX
   0x0048 00072 (test1.go:30) PCDATA $0, $1
   //將&"sj"賦給*MyStruct指向的堆中的StringHeader的Data
   0x0048 00072 (test1.go:30) MOVQ   CX, 8(AX)
   
//--------------至此,MyStruct對象構造完成------------------//

   0x004c 00076 (test1.go:32) PCDATA $0, $2
   //將go.itab.*"".MyStruct,"".myInterface+8也即type.*"".MyStruct的地址傳入CX
   //這里是經過編譯器優化的,如果開啟-N禁止優化,此處指令應為
   //LEAQ    go.itab.*"".MyStruct,"".myInterface(SB), CX
   0x004c 00076 (test1.go:32) MOVQ   go.itab.*"".MyStruct,"".myInterface+8(SB), CX
   0x0053 00083 (test1.go:32) PCDATA $0, $1
   //將type.*"".MyStruct的地址移到棧頂
   //如果開啟-N禁止優化,此處傳入*go.itab.*"".MyStruct,"".myInterface
   0x0053 00083 (test1.go:32) MOVQ   CX, (SP) 
   
   /*
     若沒有ValueOf調用,則棧頂會傳入*go.itab.*"".MyStruct,"".myInterface(無關-N),
     至此就完成了類型轉換,但是,這里調用了ValueOf,其需要轉換為eface,所以需要傳入
     *type.*"".MyStruct,所以開啟了編譯器優化后會直接傳入此地址而不是先傳itab再后移8字節傳入		   
     *type.*"".MyStruct
   */

   0x0057 00087 (test1.go:32) PCDATA $0, $0
   //將*MyStruct移到SP的8~15字節
   0x0057 00087 (test1.go:32) MOVQ   AX, 8(SP) 
   //傳入type.*"".MyStruct的地址調用ValueOf
   //如果將指針改為變量,則會觸發convT2E
   //如果禁用編譯器優化,棧頂傳入的仍為type.*"".MyStruct
   0x005c 00092 (test1.go:32) CALL   reflect.ValueOf(SB)
   /*
   func ValueOf(i interface{}) Value {
       if i == nil {
          return Value{}
       }
       return unpackEface(i)
    }
   **/

   //刷新BP,存儲棧底地址
   0x0061 00097 (test1.go:33) MOVQ   40(SP), BP
   //移動SP指針只鳶尾,棧的內存被回收
   0x0066 00102 (test1.go:33) ADDQ   $48, SP
   0x006a 00106 (test1.go:33) RET
   0x006b 00107 (test1.go:33) NOP
   0x006b 00107 (test1.go:26) PCDATA $1, $-1
   0x006b 00107 (test1.go:26) PCDATA $0, $-1
   //擴展棧
   0x006b 00107 (test1.go:26) CALL   runtime.morestack_noctxt(SB)
   0x0070 00112 (test1.go:26) JMP    0
   0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 5c 48  eH..%....H;a.v\H
   0x0010 83 ec 30 48 89 6c 24 28 48 8d 6c 24 28 48 8d 05  ..0H.l$(H.l$(H..
   0x0020 00 00 00 00 48 89 04 24 e8 00 00 00 00 48 8b 44  ....H..$.....H.D
   0x0030 24 08 48 c7 00 01 00 00 00 48 c7 40 10 02 00 00  $.H......H.@....
   0x0040 00 48 8d 0d 00 00 00 00 48 89 48 08 48 8b 0d 00  .H......H.H.H...
   0x0050 00 00 00 48 89 0c 24 48 89 44 24 08 e8 00 00 00  ...H..$H.D$.....
   0x0060 00 48 8b 6c 24 28 48 83 c4 30 c3 e8 00 00 00 00  .H.l$(H..0......
   0x0070 eb 8e                                            ..

   rel 5+4 t=16 TLS+0
   rel 32+4 t=15 type."".MyStruct+0
   rel 41+4 t=8 runtime.newobject+0
   rel 68+4 t=15 go.string."sj"+0
   rel 79+4 t=15 go.itab.*"".MyStruct,"".myInterface+8
   rel 93+4 t=8 reflect.ValueOf+0
   rel 108+4 t=8 runtime.morestack_noctxt+0

內存分布如下所示

img

5.總結

通過研究go的接口和反射,發現下面兩條原則

  • 想要實現反射需要有入口獲取元信息,接口就是這個入口,元信息在編譯后就已生成
  • 指針和變量觸發的拷貝決定了是否是對同一個對象進行的操作

#附

#1.引用

  1. A Quick Guide to Go's Assembler
  2. 從棧上理解 Go語言函數調用 - luozhiyun - 博客園 (cnblogs.com)
  3. [譯]Go 和 interface 探究 (xargin.com)
  4. 深入研究 Go interface 底層實現 (halfrost.com)
  5. Go 語言接口的原理 | Go 語言設計與實現 (draveness.me)
  6. 深度解密Go語言之關於 interface 的 10 個問題
  7. Go 語言反射的實現原理 | Go 語言設計與實現 (draveness.me)
  8. 深度解密Go語言之反射 | qcrao
  9. 有點不安全卻又一亮的 Go unsafe.Pointer - SegmentFault 思否
  10. 《Go語言實戰》

#2.觸發類型轉換的例子

func main(){
   var myInter myInterface
   myInter = &MyStruct{
      Id:   1,
      Name: "sj",
   }

   //convI2I
   var subInterface subInterface
   subInterface = myInter
   fmt.Println(subInterface)

   //convT2E
   var emptyInterface interface{}
   val := reflect.ValueOf(myInter)
   emptyInterface = val
   fmt.Println(emptyInterface)
}
//convI2I

0x0050 00080 (test1.go:36)  LEAQ   type."".subInterface(SB), CX

0x0057 00087 (test1.go:36) PCDATA $0, $1

0x0057 00087 (test1.go:36) MOVQ   CX, (SP)

0x005b 00091 (test1.go:36) PCDATA $0, $2

0x005b 00091 (test1.go:36) LEAQ   go.itab.*"".MyStruct,"".myInterface(SB), CX

0x0062 00098 (test1.go:36) PCDATA $0, $1

0x0062 00098 (test1.go:36) MOVQ   CX, 8(SP)

0x0067 00103 (test1.go:36) PCDATA $0, $0

0x0067 00103 (test1.go:36) MOVQ   AX, 16(SP)

//這里顯式調用了convI2I將iface類型轉換為iface類型

0x006c 00108 (test1.go:36) CALL   runtime.convI2I(SB)

/*
    //未觸發堆上內存分配,直接將itab和data進行復制

    func convI2I(inter *interfacetype, i iface) (r iface) {
       tab := i.tab
       if tab == nil {
          return
       }

       if tab.inter == inter {
          r.tab = tab
          r.data = i.data
          return
       }
       //根據兩個接口間的方法集合判斷兩個集合的交集是否等於目標接口的集合(方法簽名)
       r.tab = getitab(inter, tab._type, false)
       r.data = i.data
       return
    }

**/



//convT2E

0x020f 00527 (test1.go:40)  LEAQ   type.reflect.Value(SB), AX

0x0216 00534 (test1.go:40) PCDATA $0, $0

0x0216 00534 (test1.go:40) MOVQ   AX, (SP)

0x021a 00538 (test1.go:40) PCDATA $0, $1

0x021a 00538 (test1.go:40) PCDATA $1, $0

0x021a 00538 (test1.go:40) LEAQ   ""..autotmp_5+216(SP), AX

0x0222 00546 (test1.go:40) PCDATA $0, $0

0x0222 00546 (test1.go:40) MOVQ   AX, 8(SP)

0x0227 00551 (test1.go:40) CALL   runtime.convT2E(SB)

/*
    //觸發了在堆上申請內存並拷貝的動作
    func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
       if raceenabled {
          raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
       }

       if msanenabled {
          msanread(elem, t.size)
       }
       x := mallocgc(t.size, t, true)
       typedmemmove(t, x, elem)
       e._type = t
       e.data = x
       return
    }
**/

#3.interface、Type、Value三者轉換

img


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM