前言
在 【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」