Go笔记 - interface layout

157 阅读2分钟

在Go中经常使用的interface结构到底是怎样的?我们都知道interface是实现duck typing重要的数据结构。实现duck typing必须包含:类型信息和数据信息。interface结构体也应该包含这两项信息。下面先看一个例子:

package main

import "fmt"
import "unsafe"

func main() {
    a := 1
    fmt.Println(unsafe.Sizeof(&a)) // 输出:8
    size := unsafe.Sizeof(interface{}(nil)) 
    fmt.Println(size) // 输出:16
}

通过上面的例子我们能知道,一个指针的大小是8个字节,而一个interface的大小是16个字节。现在有理由怀疑interface的16个字节包含两个8字节的指针成员。下面看一个例子:

package main

import "fmt"
import "unsafe"

type name interface {
	show()
}

type jack struct {
	name string
}

func (j jack) show() {
	fmt.Println(j.name)
}

func printValue(n *name) {
	valuePtr := uintptr(unsafe.Pointer(n)) + uintptr(unsafe.Sizeof(&n))
	value := (**jack)(unsafe.Pointer(valuePtr))
	fmt.Printf("value: %v\n", (**value).name)
}

func main() {
    var j name = jack {
        name: "jack",
    }

    printValue(&j) // 输出:value: jack
}

通过上面的例子可以看到,interface9-16这8个字节保存的是一个指针,指向结构体jack的内存地址。而interface的前8个字节保存的是一个itable结构体的指针,itable包含有:类型信息和类型关联的方法(这里没办法通过Go的方法hack进去)。下面看一个关于类型例子:

package main

import "fmt"

type name interface {
	show()
}

type jack struct {
	name string
}

func (j jack) show() {
	fmt.Println(j.name)
}

func main() {
	var n name = nil
	var jn name = (*jack)(nil)

	fmt.Println(n == jn) // false
	fmt.Println(n == name(nil)) // true

	fmt.Println(n == nil) // true
	fmt.Println(jn == nil) // false
}

从上面的例子中,可以发现interface变量是否相等,必须是类型都相等才行。var n name = nil表达式,n的类型为空,值为nil;与nil作比较时,只比较,所以两者相等。而var jn name = (*jack)(nil)表达式,jn的类型为*jack,值为nil;与nil比较,类型不匹配,所以两者不相等。

参考