通过汇编和源码两大神器探究 —— Go语言接口

Tips:以下代码在go1.12.6 windows/amd64版本下测试分析,版本不同在分析源码的时候略有不同。

关于Go语言的接口的基本用法和特性这里就不说了,也不是这篇文章的重点,本文的重点是接口底层如何实现,通过汇编和源码循序渐进的分析。多余的也不多说,直接开始吧!

目录

底层数据结构

关于itab.fun字段

但是虽然知道了itab.fun字段,但是每个具体类型实现的所有方法在哪呢?

那么这个底层数据结构iface或eface什么时候对_type赋值的呢?

所以下面讲一个特殊类型Map是如何对type u struct {maptype uncommontype }赋值?

参考文章


底层数据结构

Go语言的接口分为空接口和非空接口,空接口就是没有实现任何方法的接口,非空接口实现了至少一个方法。空接口和非空接口的底层数据结构是efaceiface。eface和iface结构定义在runtime/runtime2.go:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}

type eface struct {
	_type *_type
	data  unsafe.Pointer
}

结构示意图如下:

说明:

  • _type中size是类型底层数据结构的大小,比如slice底层数据结构是指向数组指针、len和cap所以size=24;再比如string底层数据结构是指针和len所以size=16。
  • _type中kind是使用的类型对应的枚举值(Go/runtime/typekind.go),比如slice就是kindSlice,string就是kindString。

iface对应是有方法的接口即interface{...},eface是对应没有任何方法的空接口,即interface{}。注意iface和eface对应的是接口变量底层数据结构,并不是某具体类型变量对应的底层数据结构,比如下面代码中int变量g直接转成iface或eface都不能访问其成员,说明g既不是iface也不是eface(注意区分接口类型和实体类型区别),如果把g转成interface再转成eface结构是可以的。其他的接口转成eface和iface两种就是为了判断哪些属于eface哪些属于iface,详细见下面代码。
模仿源码iface和eface,写一个山寨版iface和eface:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}
type eface struct {
	_type *_type
	data  unsafe.Pointer
}
 
type imethod struct {
	name int32
	ityp int32
}
type name struct {
	bytes *byte
}
type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}
type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32
	_     [4]byte
	fun   [1]uintptr
}
type _type struct {
	size       uintptr
	ptrdata    uintptr
	hash       uint32
	tflag      uint8
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        uintptr
	gcdata    *byte
	str       int32
	ptrToThis int32
}
 
type MsgI interface {
	MsgFun()
}
 
type Msg struct {
	Name string
	Age int
}
 
func (m Msg)MsgFun()  {
	fmt.Println(m)
}
func main() {
	var a interface{} = nil
 
	var b interface{} = (*int)(nil)
 
	x := "hello"
	var c interface{} = (string)(x)
 
	m := Msg{
		Name:"XXXX",
		Age:20,
	}
	var d interface{} = (*Msg)(&m)
 
	var g int = 100
 
	ia := *(*eface)(unsafe.Pointer(&a))
 
	ib := *(*eface)(unsafe.Pointer(&b))
	ibb := *(*iface)(unsafe.Pointer(&b))
 
	ic := *(*eface)(unsafe.Pointer(&c))
	icc := *(*iface)(unsafe.Pointer(&c))
 
	id := *(*eface)(unsafe.Pointer(&d))
	idd := *(*iface)(unsafe.Pointer(&d))
 
	ig := *(*eface)(unsafe.Pointer(&g))  //注意这里g是具体类型int的变量
	igg := *(*iface)(unsafe.Pointer(&g))
 
	fmt.Println(ia, ib,ibb, ic,icc,id,idd,ig,igg)
 
	//fmt.Println(*(*_type)(unsafe.Pointer(ia._type))) //invalid memory or nil pointer dereference
 
	fmt.Println("------------------------------")
	fmt.Println(*(*_type)(unsafe.Pointer(ib._type)))
	fmt.Println(*(*itab)(unsafe.Pointer(ibb.tab)))  //从结果看,b没有itab结构体,所以不是iface是eface
	//fmt.Println(*(*interfacetype)(unsafe.Pointer(ibb.tab.inter))) // invalid memory address or nil pointer dereference
 
	fmt.Println("------------------------------")
	fmt.Println(*(*_type)(unsafe.Pointer(ic._type)))
	fmt.Println(*(*itab)(unsafe.Pointer(icc.tab)))  //从结果看,c没有itab结构体,所以不是iface是eface
	fmt.Println(*(*string)(unsafe.Pointer(ic.data)))
	//fmt.Println(*(*interfacetype)(unsafe.Pointer(icc.tab.inter))) // invalid memory address or nil pointer dereference
 
	fmt.Println("------------------------------")
	fmt.Println((*(*Msg)(unsafe.Pointer(id.data))).Age)
	fmt.Println(*(*_type)(unsafe.Pointer(id._type)))
	fmt.Println(*(*itab)(unsafe.Pointer(idd.tab)))  //从结果看,d没有itab结构体,所以不是iface是eface
	//fmt.Println(*(*interfacetype)(unsafe.Pointer(idd.tab.inter)))  //invalid memory address or nil pointer dereference
 
	fmt.Println("------------------------------")
	//fmt.Println(*(*_type)(unsafe.Pointer(ig._type)))  // invalid memory address or nil pointer dereference
	//fmt.Println(*(*itab)(unsafe.Pointer(igg.tab)))    // invalid memory address or nil pointer dereference
	//fmt.Println(*(*int)(unsafe.Pointer(ig.data)))     // invalid memory address or nil pointer dereference
 
	fmt.Println("------------------------------")
	var msgi MsgI = m
	ie := *(*iface)(unsafe.Pointer(&msgi))
	fmt.Println(ie)
	fmt.Println(*(*itab)(unsafe.Pointer(ie.tab)))
	fmt.Println(*(*_type)(unsafe.Pointer(ie.tab._type)))
	fmt.Println(*(*interfacetype)(unsafe.Pointer(ie.tab.inter))) //从结果看,能正确打印inter和_type说明,是msgi是iface,否则会出现invalid memory address or nil pointer dereference
	fmt.Println((*(*Msg)(unsafe.Pointer(ie.data))).Age,(*(*Msg)(unsafe.Pointer(ie.data))).Name)
	iee := *(*eface)(unsafe.Pointer(&msgi))
	fmt.Println(iee)
	fmt.Println(*(*_type)(unsafe.Pointer(iee._type))) //虽然可以打印,但是因为iface内存>eface,所以可以打印一些错误信息
    
        fmt.Println("------------------------------")
	//fmt.Println(*(*_type)(unsafe.Pointer(ig._type))) //panic: runtime error: invalid memory address or nil pointer dereference
	//fmt.Println(*(*int)(unsafe.Pointer(ig.data))) //panic: runtime error: invalid memory address or nil pointer dereference
}

关于itab.fun字段

此前有一段时间作者一直以为itab.fun字段表示的是具体类型实现的所有方法,最近在看绕大对反射解读才发现自己是错的。其实itab.fun字段表示的是与接口名字一样的具体类型实现的方法列表地址,在每次赋值的时候,该列表都随接口方法改变而改变(源码在runtime/iface.go对应func (m *itab) init() string函数)。下面用实例说话:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}
type eface struct {
	_type *_type
	data  unsafe.Pointer
}
 
type imethod struct {
	name int32
	ityp int32
}
type name struct {
	bytes *byte
}
type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}
type itab struct {
	inter *interfacetype
	_type *_type
	hash  uint32
	_     [4]byte
	fun   [1]uintptr
}
type _type struct {
	size       uintptr
	ptrdata    uintptr
	hash       uint32
	tflag      uint8
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        uintptr
	gcdata    *byte
	str       int32
	ptrToThis int32
}
 
type MsgI interface {
	MsgFun1()
	MsgFun2()
}
 
type Msg struct {
	Name string
	Age int
}
 
func (m Msg)MsgFun1()  {
	fmt.Println("MsgFun1",m)
}
 
func (m Msg)MsgFun2()  {
	fmt.Println("MsgFun2",m)
}
 
func (m Msg)MsgFun3()  {
	fmt.Println("MsgFun3",m)
}
 
func main() {
	m := Msg{
		Name:"XXXX",
		Age:20,
	}
 
	var msgi MsgI = m
	ie := *(*iface)(unsafe.Pointer(&msgi))
 
	fmt.Println("fun[0]地址:",(uintptr)(unsafe.Pointer(&ie.tab.fun[0])),"fun[0]值:",*(*int)(unsafe.Pointer(&ie.tab.fun[0])))
	fmt.Println("fun[1]地址:",(uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&ie.tab.fun[0])) + uintptr(8))),"fun[1]值:",*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&ie.tab.fun[0])) + uintptr(8))))
	fmt.Println("fun[2]地址:",(uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&ie.tab.fun[0])) + 2 * uintptr(8))),"fun[2]值:",*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&ie.tab.fun[0])) + 2 * uintptr(8))))
 
}
fun[0]地址: 5049112 fun[0]值: 4772080
fun[1]地址: 5049120 fun[1]值: 4772208
fun[2]地址: 5049128 fun[2]值: 0

从结果来看就很明显了。

但是虽然知道了itab.fun字段,但是每个具体类型实现的所有方法在哪呢

看看上面提到的init函数源码(runtime/iface.go):

// init fills in the m.fun array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.fun[0] to 0 and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {
	inter := m.inter
	typ := m._type
	x := typ.uncommon() //获取实体类型的外多余的不同数据(具体见下面源码分析)

	// both inter and typ have method sorted by name,
	// and interface names are unique,
	// so can iterate over both in lock step;
	// the loop is O(ni+nt) not O(ni*nt).
	ni := len(inter.mhdr)
	nt := int(x.mcount)
	xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
	j := 0
imethods:
	for k := 0; k < ni; k++ {             /* 下面这一块主要是从xmhdr中找到和inter接口类型匹配的方法 */
		i := &inter.mhdr[k]
		itype := inter.typ.typeOff(i.ityp)
		name := inter.typ.nameOff(i.name)
		iname := name.name()
		ipkg := name.pkgPath()
		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)
						*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
					}
					continue imethods
				}
			}
		}
		// didn't find method
		m.fun[0] = 0
		return iname
	}
	m.hash = typ.hash
	return ""
}

uncommon源码如下:

type method struct {
	name nameOff
	mtyp typeOff
	ifn  textOff
	tfn  textOff
}
 
type uncommontype struct {
	pkgpath nameOff
	mcount  uint16 // number of methods
	_       uint16 // unused
	moff    uint32 // offset from this uncommontype to [mcount]method
                       // moff是uncommontype起点到[mcount]method偏移量,从上面init源码 add(unsafe.Pointer(x), uintptr(x.moff))
                       // 可以看出。而[mcount]method正是包含具体类型的所有方法。
	_       uint32 // unused
}
 
func (t *_type) uncommon() *uncommontype {
	if t.tflag&tflagUncommon == 0 {
		return nil
	}
	switch t.kind & kindMask {
	case kindStruct:
		type u struct {
			structtype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindPtr:
		type u struct {
			ptrtype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindFunc:
		type u struct {
			functype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindSlice:
		type u struct {
			slicetype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindArray:
		type u struct {
			arraytype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindChan:
		type u struct {
			chantype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindMap:
		type u struct {
			maptype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	case kindInterface:
		type u struct {
			interfacetype
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	default:      // 除了上面几种特殊的类型(文章最后举一个map类型的例子分析),其余的类型t转成下面u
		type u struct {
			_type
			u uncommontype
		}
		return &(*u)(unsafe.Pointer(t)).u
	}
}

从上面例子看出,具体类型所实现的所有方法在uncommontype结构体后面。

那么这个底层数据结构iface或eface什么时候对_type赋值的呢?

实例代码:

type Myintinterface interface {
	fun()
}
 
type Myint int
 
func (m Myint)fun()  {
	fmt.Println("Myint fun")
}
 
func main(){
	var mi Myint
	var mii Myintinterface = mi
 
	mii.fun()
}

生成汇编:

"".main STEXT size=108 args=0x0 locals=0x30
        0x0000 00000 (test1008.go:17)   TEXT    "".main(SB), $48-0
        0x0000 00000 (test1008.go:17)   MOVQ    TLS, CX
        0x0009 00009 (test1008.go:17)   MOVQ    (CX)(TLS*2), CX
        0x0010 00016 (test1008.go:17)   CMPQ    SP, 16(CX)
        0x0014 00020 (test1008.go:17)   JLS     101
        0x0016 00022 (test1008.go:17)   SUBQ    $48, SP
        0x001a 00026 (test1008.go:17)   MOVQ    BP, 40(SP)
        0x001f 00031 (test1008.go:17)   LEAQ    40(SP), BP
        0x0024 00036 (test1008.go:17)   FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0024 00036 (test1008.go:17)   FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0024 00036 (test1008.go:19)   MOVQ    $0, ""..autotmp_2+32(SP)
        0x002d 00045 (test1008.go:19)   LEAQ    go.itab."".Myint,"".Myintinterface(SB), AX
        0x0034 00052 (test1008.go:19)   MOVQ    AX, (SP)
        0x0038 00056 (test1008.go:19)   LEAQ    ""..autotmp_2+32(SP), AX
        0x003d 00061 (test1008.go:19)   MOVQ    AX, 8(SP)
        0x0042 00066 (test1008.go:19)   PCDATA  $0, $0
        0x0042 00066 (test1008.go:19)   CALL    runtime.convT2I64(SB)
        0x0047 00071 (test1008.go:19)   MOVQ    16(SP), AX
        0x004c 00076 (test1008.go:19)   MOVQ    24(SP), CX
        0x0051 00081 (test1008.go:21)   MOVQ    24(AX), AX
        0x0055 00085 (test1008.go:21)   MOVQ    CX, (SP)
        0x0059 00089 (test1008.go:21)   PCDATA  $0, $0
        0x0059 00089 (test1008.go:21)   CALL    AX
        0x005b 00091 (test1008.go:22)   MOVQ    40(SP), BP
        0x0060 00096 (test1008.go:22)   ADDQ    $48, SP
        0x0064 00100 (test1008.go:22)   RET
        0x0065 00101 (test1008.go:22)   NOP
        0x0065 00101 (test1008.go:17)   PCDATA  $0, $-1
        0x0065 00101 (test1008.go:17)   CALL    runtime.morestack_noctxt(SB)
        0x006a 00106 (test1008.go:17)   JMP     0
        0x0000 65 48 8b 0c 25 28 00 00 00 48 8b 89 00 00 00 00  eH..%(...H......
        0x0010 48 3b 61 10 76 4f 48 83 ec 30 48 89 6c 24 28 48  H;a.vOH..0H.l$(H
        0x0020 8d 6c 24 28 48 c7 44 24 20 00 00 00 00 48 8d 05  .l$(H.D$ ....H..
        0x0030 00 00 00 00 48 89 04 24 48 8d 44 24 20 48 89 44  ....H..$H.D$ H.D
        0x0040 24 08 e8 00 00 00 00 48 8b 44 24 10 48 8b 4c 24  $......H.D$.H.L$
        0x0050 18 48 8b 40 18 48 89 0c 24 ff d0 48 8b 6c 24 28  .H.@.H..$..H.l$(
        0x0060 48 83 c4 30 c3 e8 00 00 00 00 eb 94              H..0........
        rel 12+4 t=16 TLS+0
        rel 48+4 t=15 go.itab."".Myint,"".Myintinterface+0
        rel 67+4 t=8 runtime.convT2I64+0
        rel 89+0 t=11 +0
        rel 102+4 t=8 runtime.morestack_noctxt+0

分析

第18行:调用runtime.convT2I64函数,源码如下:

func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {
	t := tab._type
	if raceenabled {
		raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I64))
	}
	if msanenabled {
		msanread(elem, t.size)
	}
	var x unsafe.Pointer
	if *(*uint64)(elem) == 0 {
		x = unsafe.Pointer(&zeroVal[0])
	} else {
		x = mallocgc(8, t, false)
		*(*uint64)(x) = *(*uint64)(elem)
	}
	i.tab = tab
	i.data = x
	return
}

很简单,就是通过itab和data指针构造一个iface。第一个参数通过汇编可以看出是

 0x0031 00049 (test1008.go:19)   LEAQ    go.itab."".Myint,"".Myintinterface(SB), AX

那么这里go.itab."".Myint,"".Myintinterface(SB)代表什么?

经过一番google,在github上发现国外一位star很高的大神对其进行了解释。

It looks like the compiler has already created the necessary itab for representing our iface<Mather, Adder> interface, and made it available to us via a global symbol: go.itab."".Adder,"".Mather.

We're in the process of building an iface<Mather, Adder> interface and, in order to do so, we're loading the effective address of this global go.itab."".Adder,"".Mather symbol at the top of the current stack-frame.

简单说,在编译期间,编译器已经生成了以Myint为实体的Myintinterface接口变量的iface.tab即全局符号go.itab."".Myint,"".Myintinterface,在生成汇编代码可以看到go.itab."".Myint,"".Myintinterface的声明和定义:
 

go.itab."".Myint,"".Myintinterface SRODATA dupok size=32
        0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0010 b0 50 62 21 00 00 00 00 00 00 00 00 00 00 00 00  .Pb!............
        rel 0+8 t=1 type."".Myintinterface+0
        rel 8+8 t=1 type."".Myint+0
        rel 24+8 t=1 "".(*Myint).fun+0

下面一步步分析:

  • 首先
size=32

全局符号go.itab."".Myint,"".Myintinterface占用32字节内存。如果该接口方法不止一个,因为每个方法占8个字节,所以假如接口有2个方法,size=40。

  • 再次
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010 b0 50 62 21 00 00 00 00 00 00 00 00 00 00 00 00 .Pb!............       

这是32字节内存的hexdump,也是go.itab."".Myint,"".Myintinterface对应的itab结构内存序列化。

正如你所看到的,大多数数据只是一堆0。链接器将负责填充它们,下一步将看到(rel指令)。但是已经有4个字节b0 50 62 21被设置了,对应下面itab结构知道,这个4个字节正好是itab.hash的值。

type itab struct { // 32 bytes on a 64bit arch (这里注意一下,该结构体在64位机器大小应该是32)
    inter *interfacetype // offset 0x00 ($00) 0x00~0x07 8bytes
    _type *_type	 // offset 0x08 ($08) 0x08~0x0f 8bytes
    hash  uint32	 // offset 0x10 ($16) 0x10~0x13 4bytes
    _     [4]byte	 // offset 0x14 ($20) 0x14~0x17 4bytes
    fun   [1]uintptr	 // offset 0x18 ($24) 0x18~0x1f 8bytes
}

通过源码知道,

hash  uint32 // copy of _type.hash. Used for type switches.

itab.hash是_type.hash的一份拷贝。下一步将看到_type.hash的值。

  • 最后
rel 0+8 t=1 type."".Myintinterface+0
rel 8+8 t=1 type."".Myint+0
rel 24+8 t=1 "".(*Myint).fun+0

链接器的一系列重新定位指令,即把上一步说的一堆0都赋值为相应的值。

  1. rel 0+8 t=1 type."".Myintinterface+0,告诉链接器用全局对象符号类型(type."".Myintinterface)的地址填充go.itab."".Myint,"".Myintinterface内容的0x00~0x07字节(即inter字段)。
  2. rel 8+8 t=1 type."".Myint+0,告诉链接器用全局对象符号类型(type."".Myint)的地址填充go.itab."".Myint,"".Myintinterface内容的0x08~0x0f字节(即inter字段)。
  3. rel 24+8 t=1 "".(*Myint).fun+0,告诉链接器用全局对象符号类型("".(*Myint).fun)的地址填充go.itab."".Myint,"".Myintinterface内容的0x18~0x1f字节(即fun指向的内存第一个8字节,若有多个方法,依次增加8字节来填充)。

接下来看看type."".Myintinterfacetype."".Myint这2个全局符号。

(1) 通过上面生成的汇编中间文件发现,Myint具体数据类型(_type)即type."".Myint声明和定义:

type."".Myint SRODATA size=80
        0x0000 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0010 b0 50 62 21 07 08 08 82 00 00 00 00 00 00 00 00  .Pb!............
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 01 00 00 00 10 00 00 00 00 00 00 00  ................
        0x0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        rel 24+8 t=1 runtime.algarray+80
        rel 32+8 t=1 runtime.gcbits.+0
        rel 40+4 t=5 type..namedata.*main.Myint.+0
        rel 44+4 t=5 type.*"".Myint+0
        rel 48+4 t=5 type..importpath."".+0
        rel 64+4 t=5 type..namedata.fun-+0
        rel 68+4 t=24 type.func()+0
        rel 72+4 t=24 "".(*Myint).fun+0
        rel 76+4 t=24 "".Myint.fun+0

type."".Myint大小是80,1~48字节对应_type(大小固定),49~64字节对应uncommontype(大小固定),65~80字节对应[mcount]method(大小随具体类型所实现方法个数的16倍数增加)。[上面分析已经看到uncommontypemethod的定义]

1.通过源码知道_type结构体大小是48(64位机器),也就是type."".Myint中0x00~0x2f内存对应的是_type结构体。可以看到0x10~0x13对应是_type.hash与上一步的itab.hash是相同的,链接器填充相应的字段,比如alg、gcdata(GC相关)、str和ptrToThis。


2.通过源码知道uncommontype结构体大小是16(64位机器),也就是type."".Myint中0x30~0x3f内存对应的是uncommontype结构体。运行时内存分布如下:

可以看出,mcount=1,moff=16。由上面知道:

moff    uint32 // offset from this uncommontype to [mcount]method
               // moff是uncommontype起点到[mcount]method偏移量,从上面init源码add(unsafe.Pointer(x), uintptr(x.moff))可以看出。而[mcount]method正是包含具体类型的所有方法。

moff是[mcount]method地址相对于uncommontype起点地址的偏移值,一般都是16,因为uncommontype结构体大小固定是16。

3.最后16字节内存,存放的是[mcount]method,因为Myint只实现了一个fun方法,所以mcount=1,内存分布如下:

可以看出,虽然在代码中Myint只实现了func (m Myint)fun()这一个方法,但是编译期间编译器生成了"".Myint.fun"".(*Myint).fun,并在运行期间链接器将其链接到method。这也许就是为什么Myint的值变量和指针变量都实现了Myint的值接收者方法
若将实例代码中值接收者改为指针接收者,如下:

type Myintinterface interface {
	fun()
}
 
type Myint int
 
func (m *Myint)fun()  {
	fmt.Println("Myint fun")
}
 
func main(){
	var mi Myint
 
	var mii Myintinterface = &mi
 
	//var mii Myintinterface = mi // cannot use mi (type Myint) as type Myintinterface in assignment:
	                              // Myint does not implement Myintinterface (fun method has pointer receiver)
	
	mii.fun()
}

生成汇编:

go.itab.*"".Myint,"".Myintinterface SRODATA dupok size=32
        0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0010 5a 95 d9 8b 00 00 00 00 00 00 00 00 00 00 00 00  Z...............
        rel 0+8 t=1 type."".Myintinterface+0
        rel 8+8 t=1 type.*"".Myint+0  //注意有个*
        rel 24+8 t=1 "".(*Myint).fun+0
type.*"".Myint SRODATA size=88
        0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
        0x0010 5a 95 d9 8b 01 08 08 36 00 00 00 00 00 00 00 00  Z......6........
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00  ................
        0x0040 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0050 00 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.algarray+80
        rel 32+8 t=1 runtime.gcbits.01+0
        rel 40+4 t=5 type..namedata.*main.Myint.+0
        rel 48+8 t=1 type."".Myint+0      //这一步没懂???
        rel 56+4 t=5 type..importpath."".+0
        rel 72+4 t=5 type..namedata.fun-+0
        rel 76+4 t=24 type.func()+0
        rel 80+4 t=24 "".(*Myint).fun+0
        rel 84+4 t=24 "".(*Myint).fun+0

可以看出,在代码中Myint实现了func (m *Myint)fun()这一个方法,但是跟前者不一样,编译期间编译器只生成"".(*Myint).fun这一个方法。这也许就是为什么Myint只有指针变量实现Myint的指针接收者的方法。同时也解释代码中第16行出错的问题。

关于接收者相关请看作者之前写的Go 值接收者和指针接收者 区别

(2) 通过上面生成的汇编中间文件发现,Myintinterface接口类型(interfacetype)即type."".Myintinterface声明和定义:

type."".Myintinterface SRODATA size=104
        0x0000 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00  ................
        0x0010 08 22 ad e1 07 08 08 14 00 00 00 00 00 00 00 00  ."..............
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0040 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00  ................
        0x0050 00 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  ................
        0x0060 00 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.algarray+128
        rel 32+8 t=1 runtime.gcbits.03+0
        rel 40+4 t=5 type..namedata.*main.Myintinterface.+0
        rel 44+4 t=5 type.*"".Myintinterface+0
        rel 48+8 t=1 type..importpath."".+0
        rel 56+8 t=1 type."".Myintinterface+96
        rel 80+4 t=5 type..importpath."".+0
        rel 96+4 t=5 type..namedata.fun-+0
        rel 100+4 t=5 type.func()+0
type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod
}

type."".Myintinterface大小是104,1~48字节对应typ(此_type不同于type."".Myint的_type),49~56字节对应pkgpath(包路径),57~104字节对应mhdr(接口包含的方法)。

上面分析讲解例子中,Myint底层类型是int,并不是特殊的类型(哪些是特殊类型见上面uncommon源码),

所以下面讲一个特殊类型Map是如何对type u struct {maptype uncommontype }赋值?

实例代码:

type MI interface {
	fun()
}

type M map[int]string

func (m M)fun()  {
	print("M fun")
}

func main()  {
	var m M
	m = make(map[int]string)
	m[0] = "hello"
	m[1] = "world"

        var mi MI = m
	mi.fun()
}

按照上一个问题方法来分析生成的汇编代码:

go.itab."".M,"".MI SRODATA dupok size=32
        0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0010 dd c4 05 1f 00 00 00 00 00 00 00 00 00 00 00 00  ................
        rel 0+8 t=1 type."".MI+0
        rel 8+8 t=1 type."".M+0
        rel 24+8 t=1 "".M.fun+0
type."".M SRODATA size=120
        0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00  ................
        0x0010 dd c4 05 1f 07 08 08 35 00 00 00 00 00 00 00 00  .......5........
        0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0050 08 00 10 00 d0 00 01 00 00 00 00 00 01 00 00 00  ................
        0x0060 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        0x0070 00 00 00 00 00 00 00 00                          ........
        rel 24+8 t=1 runtime.algarray+0
        rel 32+8 t=1 runtime.gcbits.01+0
        rel 40+4 t=5 type..namedata.*main.M.+0
        rel 44+4 t=5 type.*"".M+0
        rel 48+8 t=1 type.int+0
        rel 56+8 t=1 type.string+0
        rel 64+8 t=1 type.noalg.map.bucket[int]string+0
        rel 72+8 t=1 type.noalg.map.hdr[int]string+0
        rel 88+4 t=5 type..importpath."".+0
        rel 104+4 t=5 type..namedata.fun-+0
        rel 108+4 t=24 type.func()+0
        rel 112+4 t=24 "".M.fun+0  //不应该是 "".(*M).fun+0 ???
        rel 116+4 t=24 "".M.fun+0

type."".M大小是120字节。

type u struct {
			maptype
			u uncommontype
		}

type maptype struct {
	typ           _type
	key           *_type
	elem          *_type
	bucket        *_type // internal type representing a hash bucket
	hmap          *_type // internal type representing a hmap
	keysize       uint8  // size of key slot
	indirectkey   bool   // store ptr to key instead of key itself
	valuesize     uint8  // size of value slot
	indirectvalue bool   // store ptr to value instead of value itself
	bucketsize    uint16 // size of bucket
	reflexivekey  bool   // true if k==k for all keys
	needkeyupdate bool   // true if we need to update key on an overwrite
}

type."".M的1~48字节对应maptype的typ字段,49~88字节对应maptype的其余字段;89~104字节对应uncommontype字段;105~120对应[mcount]method。因为M只有一个方法,所以mcount=1。

对于上面若将实例代码中值接收者改为指针接收者生成的汇编的疑问:

经过刚才的分析,很明显,&mi是指针,所以t.kind = kindPtr,即

type u struct {
			ptrtype
			u uncommontype
}

type ptrtype struct {
	typ  _type
	elem *_type
}

 

参考文章

深度解密Go语言之关于interface的10个问题
Github外国大神对Go底层讲解

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值