首頁 > 軟體

GoLang中的iface 和 eface 的區別解析

2022-10-02 14:00:39

GoLang之iface 和 eface 的區別是什麼?

ifaceeface 都是 Go 中描述介面的底層結構體,區別在於 iface 描述的介面包含方法,而 eface 則是不包含任何方法的空介面:interface{}

從原始碼層面看一下:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type itab struct {
	inter  *interfacetype
	_type  *_type
	link   *itab
	hash   uint32 // copy of _type.hash. Used for type switches.
	bad    bool   // type does not implement interface
	inhash bool   // has this itab been added to hash?
	unused [2]byte
	fun    [1]uintptr // variable sized
}

iface 內部維護兩個指標,tab 指向一個 itab 實體, 它表示介面的型別以及賦給這個介面的實體型別。data 則指向介面具體的值,一般而言是一個指向堆記憶體的指標。

再來仔細看一下 itab 結構體:_type 欄位描述了實體的型別,包括記憶體對齊方式,大小等;inter 欄位則描述了介面的型別。fun 欄位放置和介面方法對應的具體資料型別的方法地址,實現介面呼叫方法的動態分派,一般在每次給介面賦值發生轉換時會更新此表,或者直接拿快取的 itab。

這裡只會列出實體型別和介面相關的方法,實體型別的其他方法並不會出現在這裡。如果你學過 C++ 的話,這裡可以類比虛擬函式的概念。

另外,你可能會覺得奇怪,為什麼 fun 陣列的大小為 1,要是介面定義了多個方法可怎麼辦?實際上,這裡儲存的是第一個方法的函數指標,如果有更多的方法,在它之後的記憶體空間裡繼續儲存。從組合角度來看,通過增加地址就能獲取到這些函數指標,沒什麼影響。順便提一句,這些方法是按照函數名稱的字典序進行排列的。

再看一下 interfacetype 型別,它描述的是介面的型別:

type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

可以看到,它包裝了 _type 型別,_type 實際上是描述 Go 語言中各種資料型別的結構體。我們注意到,這裡還包含一個 mhdr 欄位,表示介面所定義的函數列表, pkgpath 記錄定義了介面的包名。

這裡通過一張圖來看下 iface 結構體的全貌:

接著來看一下 eface 的原始碼:

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

相比 ifaceeface 就比較簡單了。只維護了一個 _type 欄位,表示空介面所承載的具體的實體型別。data 描述了具體的值。

我們來看個例子:

package main

import "fmt"

func main() {
	x := 200
	var any interface{} = x
	fmt.Println(any)

	g := Gopher{"Go"}
	var c coder = g
	fmt.Println(c)
}

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (p Gopher) code() {
	fmt.Printf("I am coding %s languagen", p.language)
}

func (p Gopher) debug() {
	fmt.Printf("I am debuging %s languagen", p.language)
}

執行命令,列印出組合語言:

go tool compile -S ./src/main.go

可以看到,main 函數裡呼叫了兩個函數:

func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)

上面兩個函數的引數和 ifaceeface 結構體的欄位是可以聯絡起來的:兩個函數都是將引數組裝一下,形成最終的介面。

作為補充,我們最後再來看下 _type 結構體:

type _type struct {
    // 型別大小
	size       uintptr
    ptrdata    uintptr
    // 型別的 hash 值
    hash       uint32
    // 型別的 flag,和反射相關
    tflag      tflag
    // 記憶體對齊相關
    align      uint8
    fieldalign uint8
    // 型別的編號,有bool, slice, struct 等等等等
	kind       uint8
	alg        *typeAlg
	// gc 相關
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

Go 語言各種資料型別都是在 _type 欄位的基礎上,增加一些額外的欄位來進行管理的:

type arraytype struct {
	typ   _type
	elem  *_type
	slice *_type
	len   uintptr
}

type chantype struct {
	typ  _type
	elem *_type
	dir  uintptr
}

type slicetype struct {
	typ  _type
	elem *_type
}

type structtype struct {
	typ     _type
	pkgPath name
	fields  []structfield
}

這些資料型別的結構體定義,是反射實現的基礎。

到此這篇關於GoLang之iface 和 eface 的區別是什麼的文章就介紹到這了,更多相關GoLang iface 和 eface區別內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com