You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
typeifacestruct {
tab*itabdata unsafe.Pointer
}
typeitabstruct {
inter*interfacetype_type*_typelink*itabhashuint32// copy of _type.hash. Used for type switches.badbool// type does not implement interfaceinhashbool// has this itab been added to hash?unused [2]bytefun [1]uintptr// variable sized
}
接口类型值和接口值问题,包括nil值问题
引言
先来看一个错误的程序
当
debug=true
,开启状态是没有问题的。但当
debug=false
,运行时就会出现空指针问题,out.Write([]byte("done!\n"))
原因在于out接口其动态类型为
*bytes.Buffer
, 但是其接口值为nil。 ---> 原因是: 实参未被赋值,而是这个类型的空值而nil的类型的动态类型和值都是nil,所以,
nil != out
是为true
的。iface and eface
可以从源码层面看下
普通接口配型有iface类型定义, 空接口为eface
以上为iface的定义,
iface
内部维护两个指针,tab
指向一个itab
实体, 它表示接口的类型以及赋给这个接口的实体类型。data
则指向接口具体的值,一般而言是一个指向堆内存的指针。再来仔细看一下
itab
结构体:_type
字段描述了实体的类型,包括内存对齐方式,大小等;inter
字段则描述了接口的类型。fun
字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。这里只会列出实体类型和接口相关的方法,实体类型的其他方法并不会出现在这里。如果你学过 C++ 的话,这里可以类比虚函数的概念。
另外,你可能会觉得奇怪,为什么
fun
数组的大小为 1,要是接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针,没什么影响。顺便提一句,这些方法是按照函数名称的字典序进行排列的。再看一下
interfacetype
类型,它描述的是接口的类型:可以看到,它包装了
_type
类型,_type
实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个mhdr
字段,表示接口所定义的函数列表,pkgpath
记录定义了接口的包名。接着来看一下
eface
的源码:相比
iface
,eface
就比较简单了。只维护了一个_type
字段,表示空接口所承载的具体的实体类型。data
描述了具体的值。接口的动态类型和动态值
从源码里可以看到:
iface
包含两个字段:tab
是接口表指针,指向类型信息;data
是数据指针,则指向具体的数据。它们分别被称为动态类型
和动态值
。而接口值包括动态类型
和动态值
。【引申1】接口类型和
nil
作比较 (引言中的程序错误的原因)接口值的零值是指
动态类型
和动态值
都为nil
。当仅且当这两部分的值都为nil
的情况下,这个接口值就才会被认为接口值 == nil
。例子:
输出为:
【引申2】来看一个例子,看一下它的输出:
函数运行结果:
这里先定义了一个
MyError
结构体,实现了Error
函数,也就实现了error
接口。Process
函数返回了一个error
接口,这块隐含了类型转换。所以,虽然它的值是nil
,其实它的类型是*MyError
,最后和nil
比较的时候,结果为false
。 所以返回错误要返回无错误时,应该直接返回nil,而非用先声明再返回的方式【引申3】如何打印出接口的动态类型和值?
直接看代码:
代码里直接定义了一个
iface
结构体,用两个指针来描述itab
和data
,之后将 a, b, c 在内存中的内容强制解释成我们自定义的iface
。最后就可以打印出动态类型和动态值的地址。Output:
a 的动态类型和动态值的地址均为 0,也就是 nil;b 的动态类型和 c 的动态类型一致,都是
*int
;最后,c 的动态值为 5。以上自定义inface强转的方法, 在以下参考资料的《Go 语言问题集》的‘标准库’的‘unsafe’部分有讲。是本好书,值得反复看。
参考
《Go 语言问题集》
《Go语言圣经中文版(简体)》
The text was updated successfully, but these errors were encountered: