Go語言之再談空接口
前言
在 【Go】內存中的空接口 一文中,我們知道 interface{} 類型的對象的結構如下:
- // emptyInterface is the header for an interface{} value.
- type emptyInterface struct {
- typ *rtype
- word unsafe.Pointer
- }
該結構體包含兩個指針,占 16 個字節。
該結構體包含類型信息和數據信息,Go編譯器可以把任何類型的數據封裝成該結構的對象;結果是在語法層面, interface{} 類型的變量可以引用任何類型的數據, interface{} 類型的形式參數可以接收任何類型的實際參數。
前文只是介紹了 interface{} 的對象結構,但還沒有介紹 interface{} 的類型信息。本文將詳細分析空接口的類型信息。
繼續閱讀文本之前,強烈推薦優先閱讀以下兩篇圖文:
- 【Go】內存中的空接口
- 【Go】深入理解函數
環境
- OS : Ubuntu 20.04.2 LTS; x86_64
- Go : go version go1.16.2 linux/amd64
聲明
操作系統、處理器架構、Go版本不同,均有可能造成相同的源碼編譯后運行時的寄存器值、內存地址、數據結構等存在差異。
本文僅包含 64 位系統架構下的 64 位可執行程序的研究分析。
本文僅保證學習過程中的分析數據在當前環境下的準確有效性。
代碼清單
- package main
- import "fmt"
- import "reflect"
- func main() {
- Typeof(Typeof)
- }
- //go:noinline
- func Typeof(i interface{}) {
- t := reflect.TypeOf(i)
- fmt.Println("函數類型", t.String())
- fmt.Println("參數類型", t.In(0).String())
- }
運行結果
接口類型
接口類型信息的結構定義在reflect/type.go源文件中:
- // interfaceType represents an interface type.
- type interfaceType struct {
- rtype
- pkgPath name // import path
- methods []imethod // sorted by hash
- }
該結構體占 80 個字節。
結構分布圖
將接口類型信息繪制成圖表如下:
內存分析
再次強調,繼續之前務必閱讀 【Go】內存中的函數 ,方便了解本文分析過程。
定義Typeof函數的目的是方便定位 interface{} 的類型信息,直接在該函數入口處設置斷點。
在調試過程中,首先得到的是Typeof函數的類型信息。該函數只有一個參數,參數的類型信息位于內存地址 0x4a5200 處,這就是空接口的類型信息。
將空接口的類型信息做成圖表如下:
- rtype.size = 16
- rtype.ptrdata = 16
- rtype.hash = 0x18a057e7
- rtype.tflag = 2 = reflect.tflagExtraStar
- rtype.align = 8
- rtype.fieldAlign = 8
- rtype.kind = 20 = reflect.Interface
- rtype.equal = 0x4c2ba8 = runtime.nilinterequal
- rtype.str = 0x000030b0 => *interface {}
- rtype.ptrToThis = 0x000067c0
- interfaceType.pkgPath = 0 = nil
- interfaceType.methods.Data = 0x4a5220
- interfaceType.methods.Len = 0
- interfaceType.methods.Cap = 0
從以上數據我們可以知道,interface{} 類型的對象 16 字節大小、8 字節對齊、具有可比較性,沒有任何方法。
ptrToThis = 0x000067c0 是空接口指針類型(*interface{})信息的內存偏移量。
空接口是Go語言內置的數據類型,不屬于任何包,所以包路徑值為空(pkgPath)。
可比較性
空接口具有可比較性。
空接口類型的equal函數為runtime.nilinterequal,該函數定義在runtime/alg.go源文件中:
- func nilinterequal(p, q unsafe.Pointer) bool {
- x := *(*eface)(p)
- y := *(*eface)(q)
- return x._type == y._type && efaceeq(x._type, x.data, y.data)
- }
- func efaceeq(t *_type, x, y unsafe.Pointer) bool {
- if t == nil {
- return true
- }
- eq := t.equal
- if eq == nil {
- panic(errorString("comparing uncomparable type " + t.string()))
- }
- if isDirectIface(t) {
- return x == y
- }
- return eq(x, y)
- }
- // isDirectIface reports whether t is stored directly in an interface value.
- func isDirectIface(t *_type) bool {
- return t.kind&kindDirectIface != 0
- }
通過源碼,看到空接口對象比較的邏輯如下:
- 如果兩個對象的類型不一樣,返回 false
- 如果兩個對象的類型一樣,并且值為nil,返回 true
- 如果兩個對象的類型一樣,但是不可比較,直接 panic
- 然后進行比較,并返回比較結果。
也就是說,空接口對象的比較,實際是其表示的類型和其指向的數據的比較。
通過內存中的空接口和本文,基本已經全面了解了 interface{}。
本文轉載自微信公眾號「Golang In Memory」





























