GoGoGoGogomockinterfaceGoGo
隐身接口
JavaJavaJava
public interface MyInterface {
public String hello = "Hello";
public void sayHello();
}
sayHellohelloMyInterfaceImplMyInterface
public class MyInterfaceImpl implements MyInterface {
public void sayHello() {
System.out.println(MyInterface.hello);
}
}
JavaGo
GointerfaceGo
type Animal interface {
Say() string
Name() string
}
AnimalSay() stringName() stringDuck
type Duck struct {
Name string
Sound string
}
func (a *Duck) MySay() string {
return fmt.Sprintf("My Sound is: %s", a.Sound)
}
func (a *Duck) MyName() string {
return fmt.Sprintf("My Name is: %s", a.Name)
}
AnimalMySay() stringName() stringAnimal
指针和结构体接收者
我们经常能看到两种实现接口的接收方式:指针和结构体,看下面缩略代码:
type Animal interface {
MySay() string
MyName() string
}
type Duck struct {...}
//指针方式
func (a *Duck) MySay() string {...}
func (a *Duck) MyName() string {...}
//结构体方式
func (a Duck) MySay() string {...}
func (a Duck) MyName() string {...}
Gomethod redeclared
实现接口的类型和初始化返回的类型两个维度共组成了四种情况,然而这四种情况不是都能通过编译器的检查:
结构体接收者- func (a Duck) MySay() | 指针接收者 - func (a *Duck) MySay() | |
---|---|---|
结构体指针方式初始化 var Duck Animal = &Duck{} | 通过检查 | 通过检查 |
结构体方式初始化 var Duck Animal = Duck{} | 通过检查 | 不通过 |
Duck{}&Duck{}MySay()
&Duck{}&Duck{}dereferenceDuck{}MySayDuck{}*Cat
总结起来:当我们使用指针实现接口时,只有指针类型的变量才会实现该接口;当我们使用结构体实现接口时,指针类型和结构体类型都会实现该接口。
接口嵌套
Go
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。
GoioWriterCloserWriteCloser
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type WriteCloser interface {
Writer
Closer
}
io.Writerio.Closerio.WriteCloserio.Writerio.Closerio.WriteCloserio.Writerio.Closer
package main
import (
"io"
)
// 声明一个设备结构
type device struct {
}
// 实现io.Writer的Write()方法
func (d *device) Write(p []byte) (n int, err error) {
return 0, nil
}
// 实现io.Closer的Close()方法
func (d *device) Close() error {
return nil
}
func main() {
// 声明写入关闭器, 并赋予device的实例
var wc io.WriteCloser = new(device)
// 写入数据
wc.Write(nil)
// 关闭设备
wc.Close()
// 声明写入器, 并赋予device的新实例
var writeOnly io.Writer = new(device)
// 写入数据
writeOnly.Write(nil)
}
nil 与 non-nil
Go 语言的接口类型不是任意类型。
main*NilStructniltnil
package main
import "fmt"
type NilStruct struct {
}
func NilOrNot(v interface{}) bool {
if v == nil {
return true
}
return false
}
func main() {
var t *NilStruct
fmt.Println(t == nil)
fmt.Println(NilOrNot(t))
}
$ go run main.go
true
false
我们简单总结一下上述代码执行的结果:
niltrueNilOrNotnilfalse
NilOrNot*NilStructinterface{}NilStructnil
数据结构
类型
GoType
Gointfloat64boolstringstructinterfacetype
src/runtime/type.go_typeHeader
//go 1.20.3 path: /src/runtime/type.go
type _type struct {
size uintptr //表示类型的大小,即占用内存的字节数
ptrdata uintptr //类型中指针数据的大小,以字节为单位。这个字段用于垃圾回收器识别类型中哪些部分是指针,哪些部分是非指针
hash uint32 //类型的哈希值,用于在运行时比较两个类型是否相同
tflag tflag //类型的标志位,用于存储一些额外的信息,如是否有名称、是否有不可比较的字段等
align uint8 //类型的对齐方式,以字节为单位。这个字段决定了类型在内存中的布局和对齐方式
fieldAlign uint8 //类型的字段对齐方式,以字节为单位。这个字段决定了类型中的字段在内存中的布局和对齐方式
kind uint8 //类型的种类,用于区分不同的基本类型,如 int、string、struct 等
equal func(unsafe.Pointer, unsafe.Pointer) bool //类型的相等性函数,用于在运行时比较两个值是否相等。如果为 nil,则表示该类型没有定义相等性函数,或者该类型是不可比较的
gcdata *byte //类型的垃圾回收数据,用于存储一些与垃圾回收相关的信息,如指针位图等
str nameOff //类型的名称偏移量,用于在运行时获取类型的名称。这个字段是一个相对于 _type 结构体起始地址的偏移量,可以通过它找到一个 nameOff 结构体,进而找到一个 name 结构体,其中存储了类型的名称
ptrToThis typeOff //类型的指针偏移量,用于在运行时获取指向该类型的指针类型。这个字段也是一个相对于 _type 结构体起始地址的偏移量,可以通过它找到一个 typeOff 结构体,进而找到一个 _type 结构体,其中存储了指向该类型的指针类型
}
_typeGo
有个别字段需要详细说明下,其他字段直接备注:
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
)
intstringbool_typearraychanslicefuncgolang
runtime.type_type
//go 1.20.3 path: /src/runtime/type.go
//用于表示数组类型
type arraytype struct {
typ _type // 类型描述符
elem *_type // 数组元素类型的指针
slice *_type // 切片类型的指针
len uintptr // 数组长度
}
//用于表示通道类型
type chantype struct {
typ _type // 类型描述符
elem *_type // 通道元素类型的指针
dir uintptr // 通道的方向
}
//表示切片类型
type slicetype struct {
typ _type // 类型描述符
elem *_type // 切片元素类型的指针
}
//用于表示函数类型
type functype struct {
typ _type // 类型描述符
inCount uint16 // 输入参数数量
outCount uint16 // 输出参数数量
}
//用于表示指针类型
type ptrtype struct {
typ _type // 类型描述符
elem *_type // 指针指向的类型
}
//用于表示结构体类型
type structtype struct {
typ _type
pkgPath name // 结构体所属包的路径
fields []structfield //结构体的字段列表
}
go
uncommontypeuncommontype
//自定义类型元数据
type uncommontype struct {
pkgpath nameOff
mcount uint16 // number of methods
xcount uint16 // number of exported methods
moff uint32 // offset from this uncommontype to [mcount]method
_ uint32 // unused
}
uncommontype
[]stringmysliceLenCap
mysliceslicetypeuncommontypeuncommontypemyslice
接口实现
golang
runtime.ifaceruntime.efaceinterface{}
eface — 空Interface
eface
//go1.20.1 path: src/runtime/runtime2.go
type eface struct {
_type *_type //类型元数据
data unsafe.Pointer //数据信息,指向数据指针
}
eface
_type.text.rodatadata
举个例子:
package main
import "fmt"
//Student结构体
type Student struct {
name string
}
//Student方法setName
func (s *Student) setName(name string) {
s.name = name
}
//Student方法getName
func (s *Student) getName() string {
return s.name
}
func main() {
//声明一个空接口变量a
var a interface{}
s := &Student{"Jack"}
a = s
fmt.Println(a)
}
Studentsaa
gdb
(gdb) info locals
a = {_type = 0x10c0980 <type:*+46208>, data = 0xc000014270}
s = 0xc000014270
interface
iface — 非空Interface
runtime.iface
//go1.20.1 path: src/runtime/runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer //指向原始数据指针
}
同样包含两个字段:
//go1.20.1 path: src/runtime/runtime2.go
type itab struct {
inter *interfacetype // 接口自身定义的类型信息,用于定位到具体interface类型
_type *_type // 接口的具体类型,指向实际对象类型
hash uint32 //_type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
_ [4]byte // 填充字段,保证对齐用
fun [1]uintptr //动态数组,接口方法实现列表(方法集),即函数地址列表,按字典序排序,如果数组中的内容为空表示 _type 没有实现 inter 接口
}
type interfacetype struct {
typ _type //接口类型
pkgpath name //包路径
mhdr []imethod //接口中的方法表
}
//接口类型的方法
type imethod struct {
name nameOff // 方法名称在名称表中的偏移量
ityp typeOff // 方法类型在类型表中的偏移量
}
//非接口类型的方法,它是一个压缩格式的结构,每个字段的值都是一个相对偏移量
type method struct {
name nameOff // 方法名称在名称表中的偏移量
mtyp typeOff // 方法类型在类型表中的偏移量
ifn textOff // 接口方法的实现函数在代码段中的偏移量
tfn textOff // 普通方法的实现函数在代码段中的偏移量
}
下面我们看一个例子,代码如下:
package main
import (
"fmt"
)
type Square interface {
Area() float64
Perimeter() float64
}
type Sdata struct {
x, y float64
}
func (s *Sdata) Area() float64 {
return s.x * s.y
}
func (s *Sdata) Perimeter() float64 {
return (s.x + s.y) * 2
}
func NewSdata(x, y float64) *Sdata {
return &Sdata{
x: x,
y: y,
}
}
func main() {
var s Square
Object := NewSdata(1, 2)
s = Object
fmt.Println(s)
}
gdbs
----------- 赋值前 ---------------
(gdb) p s
$2 = {tab = 0x0, data = 0x0}
----------- 赋值后 ---------------
(gdb) p s
$1 = {tab = 0x10dd9b8 <go:itab.*main.Sdata,main.Square>, data = 0xc0000b4010}
(gdb) ptype s
type = struct runtime.iface {
runtime.itab *tab;
void *data;
}
(gdb) p Object
$2 = (main.Sdata *) 0xc0000b4010
s{tab = 0x0, data = 0x0}{tab = 0x10dd9b8 , data = 0xc0000b4010}
itab
itab结构
itab缓存(itabTable)
itabitab.interitab._typeitabitabitabTableitabTable
const itabInitSize = 512
type itabTableType struct {
size uintptr // length of entries array. Always a power of 2.
count uintptr // current number of filled entries.
entries [itabInitSize]*itab // really [size] large
}
entriesentries*itabitabInitSizeitab
itab初始化
schedinititabsinit
func schedinit() {
......
itabsinit() // uses activeModules
......
}
func itabsinit() {
lockInit(&itabLock, lockRankItab)
lock(&itabLock)
for _, md := range activeModules() {
for _, i := range md.itablinks {
itabAdd(i)
}
}
unlock(&itabLock)
}
itabsinit()itabTable
itabAdd
itabAdd()itabitabTable
const itabInitSize = 512
var (
......
itabTable = &itabTableInit
itabTableInit = itabTableType{size: itabInitSize}
)
//itabAdd将给定的itab添加到itab哈希表中
func itabAdd(m *itab) {
// 检查是否存在 malloc 死锁
if getg().m.mallocing != 0 {
throw("malloc deadlock")
}
t := itabTable
if t.count >= 3*(t.size/4) { //当itab哈希表中使用率超过75%时,需要扩容处理
//以原来2倍的空间去申请新的itab哈希表空间
t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
t2.size = t.size * 2
//将旧的itab哈希表数据复制到新的itab哈希表中
iterate_itabs(t2.add)
if t2.count != t.count {
throw("mismatched count during itab table copy")
}
//发布新的哈希表。使用原子写入
atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
//采用新表作为作为当前的itab表
t = itabTable
}
//将m添加进itab表
t.add(m)
}
// add将给定的itab添加到itab表t中
func (t *itabTableType) add(m *itab) {
/**
通过与运算法来确定新itab在itab表中的index
与运算法公式:index = hash&(m-1) 其中m-1为数组长度减1,具体参考hashmap章节
*/
mask := t.size - 1 //掩码,用于计算哈希值对应的索引
h := itabHashFunc(m.inter, m._type) & mask // 获取当前索引位置的条目
for i := uintptr(1); ; i++ {
//获取h位置地址
p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
//取值
m2 := *p
//如果m信息已经存在,直接返回
if m2 == m {
return
}
//如果找到的位置为空位置,则使用原子插入
if m2 == nil {
atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
//使用值+1
t.count++
return
}
/**
如果查找的位置已经存在非m值的其余itab信息,则使用开放寻址法,进行重新寻找位置
开放寻址法公式为: h(i) = h0 + i*(i+1)/2 mod 2^k
*/
h += i
h &= mask
}
}
itabAdd()
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
return uintptr(inter.typ.hash ^ typ.hash)
}
getitab & find
getitab()intertypitabTableitab
//根据非空的接口类型和动态类型获取itab内容
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
//如果inter.mhdr为空,则表示该接口没有方法和元数据,为空接口,不存在itab
if len(inter.mhdr) == 0 {
throw("internal error - misuse of itab")
}
/**
简单情况
tflagUncommon 标识是否有uncommon内容,即记录pkgpath和方法的内容
目前看来只有匿名结构体或者reflect动态创建的struct没有methods时,该值为0
大致猜测意思就是当前查询的动态类型没有methods时,则直接返回nil
*/
if typ.tflag&tflagUncommon == 0 {
if canfail {
return nil
}
name := inter.typ.nameOff(inter.mhdr[0].name)
panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
}
var m *itab
/**
首先,查看现有表以查看是否可以找到所需的itab。
这是迄今为止最常见的情况,因此请不要使用锁。
使用atomic确保我们看到该线程完成的所有先前写入更新itabTable字段(在itabAdd中使用atomic.Storep)。
*/
t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
if m = t.find(inter, typ); m != nil {
goto finish
}
// 如果任然没有找到数据,则加锁重试
lock(&itabLock)
if m = itabTable.find(inter, typ); m != nil {
unlock(&itabLock)
goto finish
}
// 如果条目不存在,则进行输入并添加
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
m.hash = 0
m.init()
itabAdd(m)
unlock(&itabLock)
finish:
//如果非m.fun[0] == 0,则表明有方法被实现,返回m
if m.fun[0] != 0 {
return m
}
if canfail {
return nil
}
//如果数据类型并没有实现接口,那么根据调用方式,该报错报错,该panic panic。
panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}
//根据信息查找itab信息
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
/**
通过与运算法来确定在itabTable中的index
与运算法公式:index = hash&(m-1) 其中m-1为数组长度减1,具体参考hashmap章节
hash函数为:itabHashFunc
*/
mask := t.size - 1
h := itabHashFunc(inter, typ) & mask
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
// 在这里使用atomic read,所以如果我们看到m!= nil,我们也会看到m字段的初始化
// m := *p
m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
if m == nil {
return nil
}
if m.inter == inter && m._type == typ {
return m
}
/**
使用二次探测
探测公式为h(i)= h0 + i *(i + 1)/ 2 mod 2 ^ k
*/
h += i
h &= mask
}
}
总结下流程:
titabTablet.finditabTableitabTable.finditabTableitaTableitabitabTablehashpanicpanicitabunsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSizelen(inter.mhdr)fun1len(inter.mhdr)-1fun
init
itab.init()itabgetitab()
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
......
m.inter = inter
m._type = typ
m.hash = 0
m.init()
itabAdd(m)
......
}
itabitabTableitabAddinit
//go 1.20.3 path: /src/runtime/iface.go
//初始化itab,填充itab.fun数组
func (m *itab) init() string {
//赋值接口类型
inter := m.inter
//赋值动态类型
typ := m._type
//返回组合的uncommontype类型
x := typ.uncommon()
ni := len(inter.mhdr) //接口类型的方法数量
nt := int(x.mcount) //具体类型的方法数量
//指向具体类型的方法数组
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
j := 0
//指向具体itab.fun
methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]
var fun0 unsafe.Pointer
imethods:
// 这里看似双重遍历,由于方法数组都是有序的,所以其实时间复杂度为ni+nt, 而不是ni*nt
for k := 0; k < ni; k++ { //遍历接口方法
i := &inter.mhdr[k] //获取接口方法i
itype := inter.typ.typeOff(i.ityp) //i方法type
name := inter.typ.nameOff(i.name) //i方法name字段
iname := name.name() // name转化为string
ipkg := name.pkgPath() // 路径转为string
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ { // 遍历具体类型的方法
t := &xmhdr[j]
tname := typ.nameOff(t.name)
if typ.typeOff(t.mtyp) == itype && tname.name() == iname { //判等
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
if tname.isExported() || pkgPath == ipkg {
if m != nil {
ifn := typ.textOff(t.ifn)
if k == 0 {
fun0 = ifn // we'll set m.fun[0] at the end
} else {
methods[k] = ifn
}
}
continue imethods // 存在i方法就continue到外层for
}
}
}
m.fun[0] = 0 // 没有找到i方法, 将m.fun[0] 置为0
return iname
}
m.fun[0] = uintptr(fun0)
return ""
}
interfacetypetypeinterface
interface、type的方法都按字典序排,所以O(n+m)的时间复杂度可以匹配完。
funtypetypeinterface
类型转换
指针类型
先直接上代码:
package main
type People interface {
GetName() string
}
//go:noinline
func (s *Student) GetName() string {
return s.name
}
type Student struct {
name string
age int
}
func main() {
var p People
var s *Student = &Student{
name: "XJX",
age: 15,
}
p = s
p.GetName()
}
go tool compile
go tool compile -N -l -S main.go
我们开始分析汇编代码:
0x0030 00048 (main.go:32) LEAQ type."".Student(SB), AX ;AX = &type."".Student
0x0037 00055 (main.go:32) MOVQ AX, (SP) ;SP = &type."".Student
0x003b 00059 (main.go:32) PCDATA $1, $0
0x003b 00059 (main.go:32) NOP
0x0040 00064 (main.go:32) CALL runtime.newobject(SB) ;SP + 8 = &Student{}
0x0045 00069 (main.go:32) MOVQ 8(SP), DI ;DI = &Student{}
0x004a 00074 (main.go:32) MOVQ DI, ""..autotmp_3+32(SP) ;autotmp_3(32SP) = &Student{}
0x004f 00079 (main.go:33) MOVQ $3, 8(DI) ;StringHeader(DI.Name).Len = 3
0x0057 00087 (main.go:33) PCDATA $0, $-2
0x0057 00087 (main.go:33) CMPL runtime.writeBarrier(SB), $0
0x005e 00094 (main.go:33) NOP
0x0060 00096 (main.go:33) JEQ 100
0x0062 00098 (main.go:33) JMP 191
0x0064 00100 (main.go:33) LEAQ go.string."XJX"(SB), AX ;AX = &"XJX"
0x006b 00107 (main.go:33) MOVQ AX, (DI) ;StringHeader(DI.Name).Data = &"XJX"
0x006e 00110 (main.go:33) JMP 112
0x0070 00112 (main.go:32) PCDATA $0, $-1
0x0070 00112 (main.go:32) MOVQ ""..autotmp_3+32(SP), AX ;AX = &Student{}
0x0075 00117 (main.go:32) TESTB AL, (AX)
0x0077 00119 (main.go:34) MOVQ $15, 16(AX) ;DI.age = 15
0x007f 00127 (main.go:32) MOVQ ""..autotmp_3+32(SP), AX ;AX = &Student{}
0x008e 00142 (main.go:36) LEAQ go.itab.*"".Student,"".People(SB), CX;AX = *itab(go.itab.*"".Student,"".People)
0x0095 00149 (main.go:36) MOVQ CX, "".p+48(SP) ;p(48SP) = *itab(go.itab.*"".Student,"".People)
0x009a 00154 (main.go:36) MOVQ AX, "".p+56(SP) ;p(56SP) = &Student{}
0x009f 00159 (main.go:37) MOVQ "".p+48(SP), AX ; AX = *itab(go.itab.*"".Student,"".People)
0x00a4 00164 (main.go:37) TESTB AL, (AX)
0x00a6 00166 (main.go:37) MOVQ 24(AX), AX ; AX = *(GetName)
0x00aa 00170 (main.go:37) MOVQ "".p+56(SP), CX ; CX = &Student{}
0x00af 00175 (main.go:37) MOVQ CX, (SP) ; 移动CX到栈顶
0x00b3 00179 (main.go:37) CALL AX ;call GetName func
结构体类型
来看下结构体类型的代码:
package main
import "fmt"
type People interface {
GetName() string
}
//go:noinline
func (s Student) GetName() string {
return s.name
}
type Student struct {
name string
age int
}
func main() {
var p People
var s Student = Student{
name: "XJX",
age: 15,
}
p = s
p.GetName()
}
这边就不详细说了,基本跟指针类型差不多,但有几点差别:
//iface.go
func convTstring(val string) (x unsafe.Pointer) {
if val == "" {
x = unsafe.Pointer(&zeroVal[0])
} else {
x = mallocgc(unsafe.Sizeof(val), stringType, true)
*(*string)(x) = val
}
return
}
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
x := mallocgc(t.size, t, true)
typedmemmove(t, x, elem)
i.tab = tab
i.data = x
return
}
var p People = Student{
name: "XJX",
age: 15,
}
p.GetName()
类型断言
golanginterfaceinterface{}golanginterface{}specified interfacespecified interfacestruct
struct
GointerfaceGoInterfaceText
x := interfaceText.(T) //T是某一种类型,此方式断言失败会panic
or
x,err := interfaceText.(T)//T是某一种类型,此方式不会panic,错误信息会返回到err变量
上式是接口断言的一般形式,因为此方法不一定每次都可以完好运行,所以err的作用就是判断是否出错。所以一般接口断言常用以下写法:
if v,err:=InterfaceText.(T);err {//T是一种类型
possess(v)//处理v
return
}
vInterfaceTexterrtureerrfalse
我们知道接口分为空接口和非空接口,我们从两类接口开始分析。
空接口
先从例子出发,看一段空接口代码:
package main
import "fmt"
type Student struct {
name string
}
type People interface {
GetName() string
}
func (s *Student) GetName() string {
return s.name
}
func main() {
var s interface{} = &Student{name: "XJX"}
v, ok := s.(int)
if ok {
fmt.Printf("%v\n", v)
}
switch s.(type) {
case People:
}
}
通过反编译查看源码:
go tool compile -N -l -S main.go
忽略其余代码我们从类型断言这里开始看起:
......
0x0070 00112 (main.go:17) LEAQ type.*"".Student(SB), CX ;把eface _type的地址放入CX
0x0077 00119 (main.go:17) MOVQ CX, "".s+120(SP) ;把CX存放值赋值给s+120(SP)
0x007c 00124 (main.go:17) MOVQ AX, "".s+128(SP) ;把AX存放值赋值给s+128(SP)
0x0084 00132 (main.go:18) MOVQ "".s+120(SP), AX ;把s+120(SP)存放寄存器AX,即eface _type地址
0x0089 00137 (main.go:18) MOVQ "".s+128(SP), CX ;把s+128(SP)存放寄存器CX,即eface data地址
0x0091 00145 (main.go:18) LEAQ type.int(SB), DX ;把int的类型type的地址放到 DX
0x0098 00152 (main.go:18) CMPQ DX, AX ;直接比较 AX DX的地址
......
.rodata_type_type
下面看看空接口对于接口断言的汇编:
0x01b2 00434 (main.go:30) MOVL AX, ""..autotmp_14+68(SP)
0x01b6 00438 (main.go:31) MOVQ ""..autotmp_12+168(SP), AX
0x01be 00446 (main.go:31) MOVQ ""..autotmp_12+176(SP), CX
0x01c6 00454 (main.go:31) LEAQ type."".People(SB), DX ;获取People的类型元素存入DX
0x01cd 00461 (main.go:31) MOVQ DX, (SP) ;放入栈顶
0x01d1 00465 (main.go:31) MOVQ AX, 8(SP) ;放入eface
0x01d6 00470 (main.go:31) MOVQ CX, 16(SP)
0x01db 00475 (main.go:31) PCDATA $1, $0
0x01db 00475 (main.go:31) NOP
0x01e0 00480 (main.go:31) CALL runtime.assertE2I2(SB) ;判断发起调用
0x01e5 00485 (main.go:31) MOVBLZX 40(SP), AX ;bool值
0x01ea 00490 (main.go:31) MOVB AL, ""..autotmp_13+67(SP)
assertE2I2
func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
//获取eface的类型元数据
t := e._type
if t == nil {
return
}
tab := getitab(inter, t, true)
if tab == nil {
return
}
r.tab = tab
r.data = e.data
b = true
return
}
eface_typeinterfacetype
非空接口
非空接口还是直接从例子开始看:
package main
import "fmt"
type People interface {
GetName() string
}
type Student struct {
name string
}
func (s *Student) GetName() string {
return s.name
}
func main() {
var p People = &Student{name: "XJX"}
v, ok := p.(People)
if ok {
fmt.Printf("%v\n", v)
}
v, ok = p.(*Student)
if ok {
fmt.Printf("%v\n", v)
}
}
执行命令
go tool compile -N -l -S main.go
来看下关键的代码汇编片段:
.......
0x00a1 00161 (main.go:26) MOVUPS X0, ""..autotmp_4+168(SP)
0x00a9 00169 (main.go:26) MOVQ "".p+136(SP), AX
0x00b1 00177 (main.go:26) MOVQ "".p+144(SP), CX
0x00b9 00185 (main.go:26) LEAQ type."".People(SB), DX ;获取type People的类型元数据地址到 DX
0x00c0 00192 (main.go:26) MOVQ DX, (SP) ;DX存入栈底
0x00c4 00196 (main.go:26) MOVQ AX, 8(SP) ;iface存入8(SP)
0x00c9 00201 (main.go:26) MOVQ CX, 16(SP)
0x00ce 00206 (main.go:26) PCDATA $1, $1
0x00ce 00206 (main.go:26) CALL runtime.assertI2I2(SB) ;调用方法进行类型断言
0x00d3 00211 (main.go:26) MOVBLZX 40(SP), AX ;返回参数 ok bool值
0x00d8 00216 (main.go:26) MOVQ 24(SP), CX ;新的iface
0x00dd 00221 (main.go:26) MOVQ 32(SP), DX ;新的iface
.......
runtime.assertI2I2
func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {
tab := i.tab //获取变量i的iface.tab
if tab == nil {
return
}
if tab.inter != inter { //如果i的face.tab跟inter不一致
tab = getitab(inter, tab._type, true) //使用inter类型与tab._type具体类型查询itab
if tab == nil { //如果查询不到,则表示断言失败
return
}
}
//断言成功 赋值
r.tab = tab
r.data = i.data
b = true
return
}
通过这样的方式我们就实现了接口类型断言。
我们再来看代码具体类型的断言这里和空接口的例子比较类似编译器也通过汇编代码优化进行了实现,没有去调用runtime函数:
.......
0x0207 00519 (main.go:30) MOVQ $0, ""..autotmp_3+88(SP)
0x0210 00528 (main.go:30) MOVQ "".p+144(SP), AX ;放入具体的AX
0x0218 00536 (main.go:30) LEAQ go.itab.*"".Student,"".People(SB), CX ;获取具体类型的itab的地址
0x021f 00543 (main.go:30) NOP
0x0220 00544 (main.go:30) CMPQ "".p+136(SP), CX ;比较两个itab的地址进行类型断言
0x0228 00552 (main.go:30) JEQ 559
0x022a 00554 (main.go:30) JMP 871
.......
p+144(SP) p+136(SP)pitab
最后用一张图来总结下类型断言:
runtime.assertE2I2
runtime.assertI2I2
runtime.assertE2I2runtime.assertI2I2itab
参考文档:
【新乐于心】https://www.zhihu.com/people/chen-qiang-song/posts
【draveness】 https://draveness.me/golang/
【幼麟实验室】https://space.bilibili.com/567195437