Go語言之再談空接口
前言
在 【Go】?jī)?nèi)存中的空接口 一文中,我們知道 interface{} 類型的對(duì)象的結(jié)構(gòu)如下:
- // emptyInterface is the header for an interface{} value.
- type emptyInterface struct {
- typ *rtype
- word unsafe.Pointer
- }
該結(jié)構(gòu)體包含兩個(gè)指針,占 16 個(gè)字節(jié)。
該結(jié)構(gòu)體包含類型信息和數(shù)據(jù)信息,Go編譯器可以把任何類型的數(shù)據(jù)封裝成該結(jié)構(gòu)的對(duì)象;結(jié)果是在語法層面, interface{} 類型的變量可以引用任何類型的數(shù)據(jù), interface{} 類型的形式參數(shù)可以接收任何類型的實(shí)際參數(shù)。
前文只是介紹了 interface{} 的對(duì)象結(jié)構(gòu),但還沒有介紹 interface{} 的類型信息。本文將詳細(xì)分析空接口的類型信息。
繼續(xù)閱讀文本之前,強(qiáng)烈推薦優(yōu)先閱讀以下兩篇圖文:
- 【Go】?jī)?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)行時(shí)的寄存器值、內(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 個(gè)字節(jié)。
結(jié)構(gòu)分布圖
將接口類型信息繪制成圖表如下:
內(nèi)存分析
再次強(qiáng)調(diào),繼續(xù)之前務(wù)必閱讀 【Go】?jī)?nèi)存中的函數(shù) ,方便了解本文分析過程。
定義Typeof函數(shù)的目的是方便定位 interface{} 的類型信息,直接在該函數(shù)入口處設(shè)置斷點(diǎn)。
在調(diào)試過程中,首先得到的是Typeof函數(shù)的類型信息。該函數(shù)只有一個(gè)參數(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{} 類型的對(duì)象 16 字節(jié)大小、8 字節(jié)對(duì)齊、具有可比較性,沒有任何方法。
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
- }
通過源碼,看到空接口對(duì)象比較的邏輯如下:
- 如果兩個(gè)對(duì)象的類型不一樣,返回 false
- 如果兩個(gè)對(duì)象的類型一樣,并且值為nil,返回 true
- 如果兩個(gè)對(duì)象的類型一樣,但是不可比較,直接 panic
- 然后進(jìn)行比較,并返回比較結(jié)果。
也就是說,空接口對(duì)象的比較,實(shí)際是其表示的類型和其指向的數(shù)據(jù)的比較。
通過內(nèi)存中的空接口和本文,基本已經(jīng)全面了解了 interface{}。
本文轉(zhuǎn)載自微信公眾號(hào)「Golang In Memory」