• 深入理解golang: interface源码分析


    分析接口的赋值,反射,断言的实现原理

    版本:golang v1.12


    interface底层使用2个struct表示的:efaceiface

    一:接口类型分为2个#

    1. 空接口#

    Copy
    //比如
    var i interface{}
    

    2. 带方法的接口#

    Copy
    //比如
    type studenter interface {
        GetName() string
        GetAge()  int
    }
    

    二:eface 空接口定义#


    空接口通过eface结构体定义实现,位于src/runtime/runtime2.go

    Copy
    type eface struct {
    	_type *_type //类型信息
    	data  unsafe.Pointer //数据信息,指向数据指针
    }
    


    可以看到上面eface包含了2个元素,一个是_type,指向对象的类型信息,一个 data,数据指针

    三:_type 结构体#


    _type 位于 src/runtime/type.go


    _type 是go里面所有类型的一个抽象,里面包含GC,反射,大小等需要的细节,它也决定了data如何解释和操作。
    里面包含了非常多信息 类型的大小、哈希、对齐以及种类等自动。


    所以不论是空eface和非空iface都包含 _type 数据类型

    Copy
    type _type struct {
    	size       uintptr //类型大小
    	ptrdata    uintptr //含有所有指针类型前缀大小
    	hash       uint32  //类型hash值;避免在哈希表中计算
    	tflag      tflag   //额外类型信息标志
    	align      uint8   //该类型变量对齐方式
    	fieldalign uint8   //该类型结构字段对齐方式   
    	kind       uint8   //类型编号
    	alg        *typeAlg //算法表 存储hash和equal两个操作。map key便使用key的_type.alg.hash(k)获取hash值
    	// 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   //gc数据
    	str       nameOff        // 类型名字的偏移
    	ptrToThis typeOff
    }
    



    _type 中的一些数据类型如下:

    Copy
    // typeAlg is 总是 在 reflect/type.go 中 copy或使用.
    // 并保持他们同步.
    type typeAlg struct {
    	// 算出该类型的Hash
    	// (ptr to object, seed) -> hash
    	hash func(unsafe.Pointer, uintptr) uintptr
    	// 比较该类型对象
    	// (ptr to object A, ptr to object B) -> ==?
    	equal func(unsafe.Pointer, unsafe.Pointer) bool
    }
    type nameOff int32
    type typeOff int32
    


    但是各个类型需要的类型描叙是不一样的,比如chan,除了chan本身外,还需要描述其元素类型,而map则需要key类型信息和value类型信息等:

    Copy
    //src/runtime/type.go
    
    type ptrtype struct {
    	typ  _type
    	elem *_type
    }
    
    type chantype struct {
    	typ  _type
    	elem *_type
    	dir  uintptr
    }
    
    type maptype struct {
    	typ        _type
    	key        *_type
    	elem       *_type
    	bucket     *_type // internal type representing a hash bucket
    	keysize    uint8  // size of key slot
    	valuesize  uint8  // size of value slot
    	bucketsize uint16 // size of bucket
    	flags      uint32
    }
    


    看上面的类型信息,第一个自动都是 _type,接下来也定义了一堆类型所需要的信息(如子类信息),这样在进行类型相关操作时,可通过一个字(typ *_type)即可表述所有类型,然后再通过_type.kind可解析出其具体类型,最后通过地址转换即可得到类型完整的”_type树”,参考reflect.Type.Elem()函数:

    Copy
    // reflect/type.go
    // reflect.rtype结构体定义和runtime._type一致  type.kind定义也一致(为了分包而重复定义)
    // Elem()获取rtype中的元素类型,只针对复合类型(Array, Chan, Map, Ptr, Slice)有效
    func (t *rtype) Elem() Type {
    	switch t.Kind() {
    	case Array:
    		tt := (*arrayType)(unsafe.Pointer(t))
    		return toType(tt.elem)
    	case Chan:
    		tt := (*chanType)(unsafe.Pointer(t))
    		return toType(tt.elem)
    	case Map:
    		tt := (*mapType)(unsafe.Pointer(t))
    		return toType(tt.elem)
    	case Ptr:
    		tt := (*ptrType)(unsafe.Pointer(t))
    		return toType(tt.elem)
    	case Slice:
    		tt := (*sliceType)(unsafe.Pointer(t))
    		return toType(tt.elem)
    	}
    	panic("reflect: Elem of invalid type")
    }
    

    四:没有方法的interface赋值后内部结构#


    对于没有方法的interface赋值后的内部结构是怎样的呢?
    可以先看段代码:

    Copy
    import (
    	"fmt"
    	"strconv"
    )
    
    type Binary uint64
    
    func main() {
    	b := Binary(200)
    	any := (interface{})(b)
    	fmt.Println(any)
    }
    


    输出200,赋值后的结构图是这样的:

    图片来自:https://blog.csdn.net/i6448038/article/details/82916330


    对于将不同类型转化成type万能结构的方法,是运行时的convT2E方法,在runtime包中。
    以上,是对于没有方法的接口说明。
    对于包含方法的函数,用到的是另外的一种结构,叫iface

    五:iface 非空接口#


    iface结构体表示非空接口:

    iface#

    Copy
    // runtime/runtime2.go
    // 非空接口
    type iface struct {
        tab  *itab
        data unsafe.Pointer //指向原始数据指针
    }
    

    itab#


    itab结构体是iface不同于eface,比较关键的数据结构

    Copy
    // runtime/runtime2.go
    // 非空接口的类型信息
    type itab struct {
        //inter 和 _type 确定唯一的 _type类型
        inter  *interfacetype    // 接口自身定义的类型信息,用于定位到具体interface类型
        _type  *_type        // 接口实际指向值的类型信息-实际对象类型,用于定义具体interface类型
        hash int32          //_type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
        _     [4]byte
        fun  [1]uintptr //动态数组,接口方法实现列表(方法集),即函数地址列表,按字典序排序
                        //如果数组中的内容为空表示 _type 没有实现 inter 接口
                        
    }
    


    属性interfacetype类似于_type,其作用就是interface的公共描述,类似的还有maptypearraytypechantype…其都是各个结构的公共描述,可以理解为一种外在的表现信息。interfacetype源码如下:

    Copy
    // runtime/type.go
    // 非空接口类型,接口定义,包路径等。
    type interfacetype struct {
       typ     _type
       pkgpath name
       mhdr    []imethod      // 接口方法声明列表,按字典序排序
    }
    
    // 接口的方法声明,一种函数声明的抽象
    // 比如:func Print() error
    type imethod struct {
       name nameOff          // 方法名
       ityp typeOff                // 描述方法参数返回值等细节
    }
    
    type nameOff int32
    type typeOff int32
    

    method 存的是func 的声明抽象,而 itab 中的 fun 字段才是存储 func 的真实切片。


    非空接口(iface)本身除了可以容纳满足其接口的对象之外,还需要保存其接口的方法,因此除了data字段,iface通过tab字段描述非空接口的细节,包括接口方法定义,接口方法实现地址,接口所指类型等。iface是非空接口的实现,而不是类型定义,iface的真正类型为interfacetype,其第一个字段仍然为描述其自身类型的_type字段。

    六:iface整体结构图#


    图片来自:https://blog.csdn.net/i6448038/article/details/82916330

    七:含有方法的interface赋值后的内部结构#


    含有方法的interface赋值后的内部结构是怎样的呢?

    Copy
    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    type Binary uint64
    func (i Binary) String() string {
    	return strconv.FormatUint(i.Get(), 10)
    }
    
    func (i Binary) Get() uint64 {
    	return uint64(i)
    }
    
    func main() {
    	b := Binary(200)
    	any := fmt.Stringer(b)
    	fmt.Println(any)
    }
    


    首先,要知道代码运行结果为:200。
    其次,了解到fmt.Stringer是一个包含String方法的接口。

    Copy
    type Stringer interface {
    	String() string
    }
    


    最后,赋值后接口Stringer的内部结构为:

    八:参考:#

    https://wudaijun.com/2018/01/go-interface-implement/
    https://blog.csdn.net/i6448038/article/details/82916330#comments

  • 相关阅读:
    POJ2253——Frogger(Floyd变形)
    Mininet python代码创建拓扑、交互式界面创建主机、交换机
    Wireshark小技巧
    胖AP基本配置:
    IPsec
    数据链路层、ARP/RARP、ICMP、ping和traceroute
    IPv4地址范围和一些小知识
    UDP:rfc768/广播和多播/IGMP
    TCP
    人生苦短之我用Python篇(XML模块)
  • 原文地址:https://www.cnblogs.com/ExMan/p/13905698.html
Copyright © 2020-2023  润新知