背景:
golang的interface是一種satisfied式的。A類只要實現了IA interface定義的方法,A就satisfied了接口IA。更抽象一層,如果某些設計上需要一些更抽象的共性,比如print各類型,這時需要使用reflect機制,reflect實質上就是將interface的實現暴露了一部分給應用代碼。要理解reflect,需要深入了解interface。
go的interface是一種隱式的interface,但golang的類型是編譯階段定的,是static的,如:
type MyInt int var i int var j MyInt
雖然MyInt底層就是int,但在編譯器角度看,i的類型是int,j的類型是MyInt,是靜態、不一致的。兩者要賦值必須要進行類型轉換。
即使是interface,就語言角度來看也是靜態的。如:
var r io.Reader
不管r后面用什么來初始化,它的類型總是io.Reader。
更進一步,對於空的interface,也是如此。
記住go語言類型是靜態這一點,對於理解interface/reflect很重要。
看一例:
var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty
到這里,r的類型是什么?r的類型仍然是interface io.Reader,只是r = tty這一句,隱含了一個類型轉換,將tty轉成了io.Reader。
interface的實現:
作為一門編程語言,對方法的處理一般分為兩種類型:一是將所有方法組織在一個表格里,靜態地調用(C++, java);二是調用時動態查找方法(python, smalltalk, js)。
而go語言是兩者的結合:雖然有table,但是是需要在運行時計算的table。
如下例:
Binary類實現了兩個方法,String()和Get()
type Binary uint64
func (i Binary) String() string {
return strconv.Uitob64(i.Get(), 2)
}
func (i Binary) Get() uint64 {
return uint64(i)
}
因為它實現了String(),按照golang的隱式方法實現來看,Binary satisfied了Stringer接口。因此它可以賦值: s:=Stringer(b)。
以此為例來說明下interface的實現:
interface的內存組織如圖:
一個interface值由兩個指針組成,第一個指向一個interface table,叫
itable。itable開頭是一些描述類型的元字段,后面是一串方法。注意這個方法是interface本身的方法,並非其dynamic value(Binary)的方法。即這里只有String()方法,而沒有Get方法。但這個方法的實現肯定是具體類的方法,這里就是Binary的方法。
當這個interface無方法時,itable可以省略,直接指向一個type即可。
另一個指針data指向dynamic value的一個拷貝,這里則是b的一份拷貝。也就是,給interface賦值時,會在堆上分配內存,用於存放拷貝的值。
同樣,當值本身只有一個字長時,這個指針也可以省略。
一個interface的初始值是兩個nil。比如,
var w io.Writer
這時,tab和data都是nil。interface是否為nil取決於itable字段。所以不一定data為nil就是nil,判斷時要額外注意。
這樣,像這樣的代碼:
switch v := any.(type) {
case int:
return strconv.Itoa(v)
case float:
return strconv.Ftoa(v, 'g', -1)
}
實際上是any這個interface取了 any. tab->type。
而interface的函數調用實際上就變成了:
s.tab->fun[0](s.data)。第一個參數即自身類型指針。
itable的生成:
itable的生成是理解interface的關鍵。
如剛開始處提的,為了支持go語言這種接口間僅通過方法來聯系的特性,是沒有辦法像C++一樣,在編譯時預先生成一個method table的,只能在運行時生成。因此,自然的,所有的實體類型都必須有一個包含此類型所有方法的“類型描述符”(type description structure);而interface類型也同樣有一個類似的描述符,包含了所有的方法。
這樣,interface賦值時,計算interface對象的itable時,需要對兩種類型的方法列表進行遍歷對比。如后面代碼所示,這種計算只需要進行一次,而且優化成了O(m+n)。
可見,interface與itable之間的關系不是獨立的,而是與interface具體的value類型有關。
即(interface類型, 具體類型)->itable。
1 var any interface{} // initialized elsewhere
2 s := any.(Stringer) // dynamic conversion
3 for i := 0; i < 100; i++ {
4 fmt.Println(s.String())
5 }
itable的計算不需要到函數調用時進行,只需要在interface賦值時進行即可,如上第2行,不需要在第4行進行。
最后,看一些實現代碼:
以下是上面圖中的兩個字段。
143 type iface struct {
144 tab *itab // 指南itable
145 data unsafe.Pointer // 指向真實數據
146 }
再看itab的實現:
617 type itab struct {
618 inter *interfacetype 619 _type *_type
620 link *itab
621 bad int32
622 unused int32
623 fun [1]uintptr // variable sized
624 }
可見,它使用一個疑似鏈表的東西,可以猜這是用作hash表的拉鏈。
前兩個字段應該是用來表達具體的interface類型和實際擁有的值的類型的,即一個itable的key。(上文提到的(interface類型, 具體類型) )
310 type imethod struct {
311 name nameOff
312 ityp typeOff
313 }
314
315 type interfacetype struct {
316 typ _type
317 pkgpath name
318 mhdr []imethod
319 }
320
interfacetype如有若干imethod,可以猜想這是表達interface定義的方法數據結構。
28 type _type struct {
29 size uintptr
30 ptrdata uintptr // size of memory prefix holding all pointers
31 hash uint32
32 tflag tflag
33 align uint8
34 fieldalign uint8
35 kind uint8
36 alg *typeAlg
37 // gcdata stores the GC type data for the garbage collector.
38 // If the KindGCProg bit is set in kind, gcdata is a GC program.
39 // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
40 gcdata *byte
41 str nameOff
42 ptrToThis typeOff
43 }
對於_type,可見里面有gc的東西,應該就是具體的類型了。這里有個hash字段,itable實現就是掛在一個全局的hash table中。hash時用到了這個字段:
22 func itabhash(inter *interfacetype, typ *_type) uint32 {
23 // compiler has provided some good hash codes for us.
24 h := inter.typ.hash
25 h += 17 * typ.hash
26 // TODO(rsc): h += 23 * x.mhash ?
27 return h % hashSize
28 }
可見,這里有個把interface類型與具體類型之間的信息結合起來做一個hash的過程,這個hash就是上述的itab的存儲地點,itab中的link就是hash中的拉鏈。
回到itab,看取一個itab的邏輯:
如果發生了typeassert或是interface的賦值(強轉),需要臨時計算一個itab。這時會先在hash表中找,找不到才會真實計算。
44 h := itabhash(inter, typ)
45
46 // look twice - once without lock, once with.
47 // common case will be no lock contention.
48 var m *itab
49 var locked int
50 for locked = 0; locked < 2; locked++ {
51 if locked != 0 {
52 lock(&ifaceLock)
53 }
54 for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
55 if m.inter == inter && m._type == typ {
71 return m // 找到了前面計算過的itab
72 }
73 }
74 }
75 // 沒有找到,生成一個,並加入到itab的hash中。
76 m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
77 m.inter = inter
78 m._type = typ
79 additab(m, true, canfail)
這個hash是個全局變量:
13 const ( 14 hashSize = 1009 15 ) 16 17 var ( 18 ifaceLock mutex // lock for accessing hash 19 hash [hashSize]*itab 20 )
最后,看一下如何生成itab:
92 // both inter and typ have method sorted by name,
93 // and interface names are unique,
94 // so can iterate over both in lock step;
95 // the loop is O(ni+nt) not O(ni*nt). // 按name排序過的,因此這里的匹配只需要O(ni+nt)
99 j := 0
100 for k := 0; k < ni; k++ {
101 i := &inter.mhdr[k]
102 itype := inter.typ.typeOff(i.ityp)
103 name := inter.typ.nameOff(i.name)
104 iname := name.name()
109 for ; j < nt; j++ {
110 t := &xmhdr[j]
111 tname := typ.nameOff(t.name)
112 if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
118 if m != nil {
119 ifn := typ.textOff(t.ifn)
120 *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn // 找到匹配,將實際類型的方法填入itab的fun
121 }
122 goto nextimethod
123 }
124 }
125 }
135 nextimethod:
136 }
140 h := itabhash(inter, typ) //插入上面的全局hash
141 m.link = hash[h]
142 atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
143 }
到這里,interface的數據結構的框架。
reflection實質上是將interface背后的實現暴露了一部分給應用代碼,使應用程序可以使用interface實現的一些內容。只要理解了interface的實現,reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value.
參考:
