Go語言之再談空接口
前言
在 【Go】內(nèi)存中的空接口 一文中,我們知道 interface{} 類型的對象的結(jié)構(gòu)如下:
- // emptyInterface is the header for an interface{} value.
 - type emptyInterface struct {
 - typ *rtype
 - word unsafe.Pointer
 - }
 
該結(jié)構(gòu)體包含兩個指針,占 16 個字節(jié)。
該結(jié)構(gòu)體包含類型信息和數(shù)據(jù)信息,Go編譯器可以把任何類型的數(shù)據(jù)封裝成該結(jié)構(gòu)的對象;結(jié)果是在語法層面, interface{} 類型的變量可以引用任何類型的數(shù)據(jù), interface{} 類型的形式參數(shù)可以接收任何類型的實(shí)際參數(shù)。
前文只是介紹了 interface{} 的對象結(jié)構(gòu),但還沒有介紹 interface{} 的類型信息。本文將詳細(xì)分析空接口的類型信息。
繼續(xù)閱讀文本之前,強(qiáng)烈推薦優(yōu)先閱讀以下兩篇圖文:
- 【Go】內(nèi)存中的空接口
 - 【Go】深入理解函數(shù)
 
環(huán)境
- OS : Ubuntu 20.04.2 LTS; x86_64
 - Go : go version go1.16.2 linux/amd64
 
聲明
操作系統(tǒng)、處理器架構(gòu)、Go版本不同,均有可能造成相同的源碼編譯后運(yùn)行時的寄存器值、內(nèi)存地址、數(shù)據(jù)結(jié)構(gòu)等存在差異。
本文僅包含 64 位系統(tǒng)架構(gòu)下的 64 位可執(zhí)行程序的研究分析。
本文僅保證學(xué)習(xí)過程中的分析數(shù)據(jù)在當(dāng)前環(huán)境下的準(zhǔn)確有效性。
代碼清單
- package main
 - import "fmt"
 - import "reflect"
 - func main() {
 - Typeof(Typeof)
 - }
 - //go:noinline
 - func Typeof(i interface{}) {
 - t := reflect.TypeOf(i)
 - fmt.Println("函數(shù)類型", t.String())
 - fmt.Println("參數(shù)類型", t.In(0).String())
 - }
 
運(yùn)行結(jié)果
接口類型
接口類型信息的結(jié)構(gòu)定義在reflect/type.go源文件中:
- // interfaceType represents an interface type.
 - type interfaceType struct {
 - rtype
 - pkgPath name // import path
 - methods []imethod // sorted by hash
 - }
 
該結(jié)構(gòu)體占 80 個字節(jié)。
結(jié)構(gòu)分布圖
將接口類型信息繪制成圖表如下:
內(nèi)存分析
再次強(qiáng)調(diào),繼續(xù)之前務(wù)必閱讀 【Go】內(nèi)存中的函數(shù) ,方便了解本文分析過程。
定義Typeof函數(shù)的目的是方便定位 interface{} 的類型信息,直接在該函數(shù)入口處設(shè)置斷點(diǎn)。
在調(diào)試過程中,首先得到的是Typeof函數(shù)的類型信息。該函數(shù)只有一個參數(shù),參數(shù)的類型信息位于內(nèi)存地址 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
 
從以上數(shù)據(jù)我們可以知道,interface{} 類型的對象 16 字節(jié)大小、8 字節(jié)對齊、具有可比較性,沒有任何方法。
ptrToThis = 0x000067c0 是空接口指針類型(*interface{})信息的內(nèi)存偏移量。
空接口是Go語言內(nèi)置的數(shù)據(jù)類型,不屬于任何包,所以包路徑值為空(pkgPath)。
可比較性
空接口具有可比較性。
空接口類型的equal函數(shù)為runtime.nilinterequal,該函數(shù)定義在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
 - 然后進(jìn)行比較,并返回比較結(jié)果。
 
也就是說,空接口對象的比較,實(shí)際是其表示的類型和其指向的數(shù)據(jù)的比較。
通過內(nèi)存中的空接口和本文,基本已經(jīng)全面了解了 interface{}。
本文轉(zhuǎn)載自微信公眾號「Golang In Memory」




















 
 
 






 
 
 
 