type Message struct {
A *Message
}
func (x *Message) GetA() *Message {
if x != nil {
return x.A
}
return nil
}
func TestNil(t *testing.T) {
var s *Message
var v interface{} = s
fmt.Println(v == s) // #=> true
fmt.Println(s == nil) // #=> true
fmt.Println(v == nil) // #=> false
fmt.Println(s.GetA().GetA().GetA() == nil) // #=> true
}
s
是nil
,为什么赋值给v
就不是nil
了?s
是nil
,v
不是nil
,为什么s
还等于v
?s.GetA()
返回的是nil
,为什么nil还能继续调用GetA()
方法?下面是我们常见的一种golang错误处理的坑,即自定义错误对象:
type Err struct {
err string
}
func (e *Err) Error() string {
return e.err
}
func returnErr() *Err {
return nil
}
func TestErr(t *testing.T) {
var err error
err = returnErr()
fmt.Println(err, err != nil) // #=> true
}
这里和上面是相同的问题,即返回的nil为什么不等于nil?
nil在golang中是一个预设值:
// nil is a predeclared identifier representing the zero value for a
// pointer, channel, func, interface, map, or slice type.
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type
// Type is here for the purposes of documentation only. It is a stand-in
// for any Go type, but represents the same type for any given function
// invocation.
type Type int
可以看到,nil初始是0。
golang的interface是一种内置类型,严格来讲它算是goalng提供的一种语法糖,辅助编码用的,它在运行时会转换成两种类型(位于包/usr/local/go/src/runtime/runtime2.go
中):
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
iface
是指包含方法的接口,eface
是指不含方法的接口,特指interface{}
,goalng将其独立出来节省部分存储空间。
我们这里专注讨论interface{}
,我们在编码中可以将任意类型转成interface{}
,但是interface{}
并不是任意类型,它在被赋值后就是一种特有类型。其中_type起到至关重要的作用,它是 Go 语言类型的运行时表示。下面是运行时包中的结构体,其中包含了很多类型的元信息,例如:类型的大小、哈希、对齐以及种类等。
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
size
字段存储了类型占用的内存空间,为内存空间的分配提供信息;
hash
字段能够帮助我们快速确定类型是否相等;
equal
字段用于判断当前类型的多个对象是否相等;
我们将上面的示例编译成汇编语言,查看汇编代码:
go tool compile -S -N -l nil_test.go
➜ nil git:(master) ✗ go tool compile -S -N -l nil_test.go .
"".TestNil STEXT size=634 args=0x8 locals=0xe8 funcid=0x0 align=0x0
TEXT "".TestNil(SB), ABIInter # func TestNil(t *testing.T) {
MOVQ $0, "".s+32(SP) # var s *Message : 将0赋值给[s栈32位置]
LEAQ type.*"".Message(SB), DX # var v interface{} = s : 将Message类型有效地址赋值给DX寄存器
MOVQ DX, "".v+96(SP) # var v interface{} = s : 将DX寄存器内容(Message类型地址)赋值给[v栈96位置]
MOVQ $0, "".v+104(SP) # var v interface{} = s : 将0赋值给[v栈的104位置](给v赋值)
MOVQ "".s+32(SP), SI # fmt.Println(v == s) : 将[s栈32位置]赋值给SI寄存器(值为0)
CMPQ "".v+104(SP), SI # fmt.Println(v == s) : 对比[v栈104位置]和[s栈32位置值](都是0)
SETEQ DL # fmt.Println(v == s) : 将对比值赋值给DL寄存器
CALL fmt.Println(SB) # fmt.Println(v == s) : 打印对比结果,true
CMPQ "".s+32(SP), $0 # fmt.Println(s == nil) : 对比[s栈32位置]和0值(都是0)
CALL fmt.Println(SB) # fmt.Println(s == nil) : 打印对比结果,true
CMPQ "".v+96(SP), $0 # fmt.Println(v == nil) : 对比[v栈96位置]和0值([v栈96位置]是有值的,因此结果是false)
CALL fmt.Println(SB) # fmt.Println(v == nil) : 打印对比结果,false
MOVQ "".s+32(SP), AX # fmt.Println(s.GetA().GetA().GetA() == nil) :
CALL "".(*Message).GetA(SB) # fmt.Println(s.GetA().GetA().GetA() == nil) : 汇编会将nil转成*Message类型
结合interface{}定义和汇编结果我们可以发现:
nil
对比输出是true
nil
,但是v是有值的,它拿到了一个Message
类型的地址v栈96位置nil
作比较对比的是v的整个栈内容是否为0,所以输出false
Get
方法会编译成Message
指针调用Get
方法,所以不会报错从编码角度看:
data unsafe.Pointer
指向s的地址,成员变量_type *_type
不为空,size,hash,equal都是有值的,所以v不是nil。下面是堆栈的图示:
堆栈高度 | 0-32 | 32-56 | 56-104 | 104-112 | 112-120 |
---|---|---|---|---|---|
堆栈内容 | 函数 | s的值(0) | 0 | v的_type(Message类型指针) | v的值(0) |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。