前言
在 【Go】内存中的空接口 一文中,我们知道 interface{} 类型的对象的结构如下:
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
- 1.
- 2.
- 3.
- 4.
- 5.
该结构体包含两个指针,占 16 个字节。
该结构体包含类型信息和数据信息,Go编译器可以把任何类型的数据封装成该结构的对象;结果是在语法层面, interface{} 类型的变量可以引用任何类型的数据, interface{} 类型的形式参数可以接收任何类型的实际参数。
前文只是介绍了 interface{} 的对象结构,但还没有介绍 interface{} 的类型信息。本文将详细分析空接口的类型信息。
继续阅读文本之前,强烈推荐优先阅读以下两篇图文:
- 【Go】内存中的空接口
- 【Go】深入理解函数
环境
OS : Ubuntu 20.04.2 LTS; x86_64
Go : go version go1.16.2 linux/amd64
- 1.
- 2.
声明
操作系统、处理器架构、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())
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
运行结果
接口类型
接口类型信息的结构定义在reflect/type.go源文件中:
// interfaceType represents an interface type.
type interfaceType struct {
rtype
pkgPath name // import path
methods []imethod // sorted by hash
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
该结构体占 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
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
通过源码,看到空接口对象比较的逻辑如下:
- 如果两个对象的类型不一样,返回 false
- 如果两个对象的类型一样,并且值为nil,返回 true
- 如果两个对象的类型一样,但是不可比较,直接 panic
- 然后进行比较,并返回比较结果。
也就是说,空接口对象的比较,实际是其表示的类型和其指向的数据的比较。
通过内存中的空接口和本文,基本已经全面了解了 interface{}。
本文转载自微信公众号「Golang In Memory」