接口的值
接口的值简单来说,是由两部分组成的,就是类型和数据。
那么判断两个接口是相等,就是看他们的这两部分是否相等;另外类型和数据都为nil才代表接口是nil,这里就解释了上面的问题。由于golang的err实现是一个接口,所以很容易在err的处理过程中写错。
接口的数据结构
go的接口有两种结构,一种是有方法定义的接口,一种是空接口,分别对应两种实现。
空接口的实现
空接口通过eface结构定义实现,位于/src/runtime/runtime2.go
type eface struct { _type *_type data unsafe.Pointer }
eface包含了2个元素,一个是_type,指向对象的类型元数据,一个 data,数据指针。
type _type struct { size uintptr // 类型占用内存大小 ptrdata uintptr // 包含所有指针的内存前缀大小 hash uint32 // 类型 hash tflag tflag // 标记位,主要用于反射 align uint8 // 对齐字节信息 fieldAlign uint8 // 当前结构字段的对齐字节数 kind uint8 // 基础类型枚举值 equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较两个形参对应对象的类型是否相等 gcdata *byte // GC 类型的数据 str nameOff // 类型名称字符串在二进制文件段中的偏移量 ptrToThis typeOff // 类型元信息指针在二进制文件段中的偏移量 }
1,kind,这个字段描述的是如何解析基础类型。在 Go 语言中,基础类型是一个枚举常量,有 26 个基础类型,如下。枚举值通过 kindMask 取出特殊标记位。
const ( kindBool = 1 + iota kindInt kindInt8 kindInt16 kindInt32 kindInt64 kindUint kindUint8 kindUint16 kindUint32 kindUint64 kindUintptr kindFloat32 kindFloat64 kindComplex64 kindComplex128 kindArray kindChan kindFunc kindInterface kindMap kindPtr kindSlice kindString kindStruct kindUnsafePointer kindDirectIface = 1 << 5 kindGCProg = 1 << 6 kindMask = (1 << 5) - 1 )
以下面两个例子为例:
指针的类型元数据在_type结构体后面,记录着一个*_type,指向其存储元素的类型元数据。
如一个空接口被赋值了指向int的一个指针,那么ptrtype中的elem就指向int的元数据结构体。
type ptrtype struct { type __type elem *_type }
非空接口
空接口由于不包含方法,所以结构简单。非空接口的底层实现:
type iface struct { tab *itab data unsafe.Pointer }
tab中存放的是类型,方法等信息,data指针指向的是iface绑定对象的原始数据。
type itab struct { //inter 和 _type 确定唯一的 _type类型 inter *interfacetype //接口自身定义的类型信息,用于定位到具体interface类型 _type *_type // 接口实际指向值的类型信息-实际对象类型,用于定义具体interface类型 hash uint32 //_type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致 _ [4]byte fun [1]uintptr //动态数组,接口方法实现列表(方法集),即函数地址列表,按字典序排序 //如果数组中的内容为空表示 _type 没有实现 inter 接口 }
itab.inter是interface的类型元数据,它记录了这个接口类型的描述信息,接口要求的方法列表就记录在interfacetype.mhdr这里。
type interfacetype struct { typ _type pkgpath name mhdr []imethod }
tab._type就是接口的动态类型,也就是被赋给接口类型的那个变量的类型元数据。itab中的_type和iface中的data能简要描述一个变量。_type是这个变量对应的类型,data是这个变量的值。
itab.hash是从itab.type中拷贝来的,是类型的哈希值,用于快速判断类型是否相等时使用。
itab.fun记录的是动态类型实现的那些接口要求的方法的地址,是从方法元数据中拷贝来的,为的是快速定位到方法。
如果itab._type对应的类型没有实现这个接口,则itab.fun[0]=0,这在类型断言时会用到。当fun[0]为0时,说明_type并没有实现该接口,当实现接口时,fun存放了第一个接口方法的地址。
itab缓存
对于itab来说,既然一个非空接口类型和一个动态类型就可以确定一个itab的内容,那么这个itab结构体自然是可以被接口类型和动态类型均相同的接口变量复用的。这就是itabTable.
const itabInitSize = 512 type itabTableType struct { size uintptr count uintptr entries [itabInitSize]*itab }
可以看出这个全局的itabTable是以数组的形式存储的,size记录数组的大小,总是2的次幂
count记录数组中已使用了多少。
entries 是一个 *itab数组,初始大小为512.
需要一个itab时,会首先去itabTable里查找,计算哈希值时会用到接口类型(itab.inter)和动态类型(itab._type)的类型哈希值。
func itabHashFunc(inter *interfacetype, typ *_type) uintptr { return uintptr(inter.typ.hash ^ typ.hash) }
如果能查询到对应的itab指针,就直接拿来使用。若没有就要再创建,然后添加到itabTable中。
类型断言
一,空接口.(具体类型)
每个具体类型的元数据都是全局唯一的。
二,非空接口.(具体类型)
三,空接口.(非空接口)
先去itab缓存中查找,查到后检查是否itab.fun[0] == 0。若缓存中不存在,就去检查_type对应的类型元数据的方法是否在非空接口中都有对应。
四,非空接口.(非空接口)
标签:itab,struct,非空,接口,类型,go,type From: https://www.cnblogs.com/dadishi/p/17060558.html