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 // 接口中的方法調用入口,若有多個方法順序向后排列在內存中
}

圖中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 Type
和Value
4.1.1 Type
、rtype
和emptyInterface
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
內存分布如下所示
5.總結
通過研究go的接口和反射,發現下面兩條原則
- 想要實現反射需要有入口獲取元信息,接口就是這個入口,元信息在編譯后就已生成
- 指針和變量觸發的拷貝決定了是否是對同一個對象進行的操作
#附
#1.引用
- A Quick Guide to Go's Assembler
- 從棧上理解 Go語言函數調用 - luozhiyun - 博客園 (cnblogs.com)
- [譯]Go 和 interface 探究 (xargin.com)
- 深入研究 Go interface 底層實現 (halfrost.com)
- Go 語言接口的原理 | Go 語言設計與實現 (draveness.me)
- 深度解密Go語言之關於 interface 的 10 個問題
- Go 語言反射的實現原理 | Go 語言設計與實現 (draveness.me)
- 深度解密Go語言之反射 | qcrao
- 有點不安全卻又一亮的 Go unsafe.Pointer - SegmentFault 思否
- 《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三者轉換
