内容简介:[TOC]本文基于golang 1.11源码进行分析。先演示用法和注意事项,再深入源码分析原理。在golang中,
[TOC]
本文基于golang 1.11源码进行分析。先演示用法和注意事项,再深入源码分析原理。
在golang中, 接口本身也是一种类型,它代表的是一个方法的集合。任何类型只要实现了接口中声明的所有方法,那么该类就实现了该接口 。与其他语言不同, golang并不需要显示声明类型实现了某个接口 ,而是由编译器和runtime进行检查。不用显示什么这点非常棒,这样就无侵入非常方便。
1 使用
1.1 声明
type 接口名 interface {
方法1
方法2
...
方法n
}
type 接口名 interface {
已声明接口名1
...
已声明接口名n
}
如果 一个接口不包含任何方法,那么就是一个空接口(empty interface) ,所有类型都符合empty interface的定义,因此 任何类型都能转换成empty interface ,可以看到常常使用empty interface来实现多态,例如之前我们分析过的map源码。
对于接口的命名,一般我们都是以er结尾,例如Writer、Reader等等。
1.2 实现接口
我们看个例子:
package main
import (
"fmt"
)
type Tester interface {
Display()
DisplayAppend(string)
DisplayAppend2(string) string
}
type Tester2 interface {
DisplayAppend(string)
}
type Test struct {
s string
}
func (t *Test) Display() {
fmt.Printf("Display:%p, %#v\n", t ,t)
}
func (t Test) DisplayAppend(s string) {
t.s += s
fmt.Printf("DisplayAppend:%p, %#v\n", &t, t)
}
func (t *Test) DisplayAppend2 (s string) string {
t.s += s
fmt.Printf("DisplayAppend2:%p, %#v\n", t, t)
return t.s
}
func TestInterface(t Tester) {
t.Display()
t.DisplayAppend(" TestInterface")
t.DisplayAppend2(" TestInterface")
}
func TestInterface2(t Tester2) {
t.DisplayAppend("TestInterface2")
}
func main() {
var test Test
test.s = "aaa"
fmt.Printf("%p\n", &test)
test.Display()
test.DisplayAppend(" raw")
TestInterface(&test)
//TestInterface(test) //cannot use test (type Test) as type Tester in argument to TestInterface:Test does not implement Tester (Display method has pointer receiver)
TestInterface2(&test)
TestInterface2(test)
}
输出
0xc42000e1e0
Display:0xc42000e1e0, &main.Test{s:"aaa"}
DisplayAppend:0xc42000e200, main.Test{s:"aaa raw"}
Display:0xc42000e1e0, &main.Test{s:"aaa"}
DisplayAppend:0xc42000e230, main.Test{s:"aaa TestInterface"}
DisplayAppend2:0xc42000e1e0, &main.Test{s:"aaa TestInterface"}
DisplayAppend:0xc42000e260, main.Test{s:"aaa TestInterfaceTestInterface2"}
DisplayAppend:0xc42000e290, main.Test{s:"aaa TestInterfaceTestInterface2"}
在这个例子中,我们定义了一个类型Test,Test类型有三个方法,两个方法的接受者是*Test,一个方法的接受者是Test;定义两个接口类型,Tester和Tester2 ,Tester有三个Test中的同名方法,Tester2中只有接受者是Test的同名方法。然后有TestInterface函数,入参是Tester,TestInterface2函数入参是Tester2。
从编译和运行结果可以看到,TestInterface函数入参只能填Test类型,TestInterface2入参既可以是Test也可以是*Test。TestInterface传入Test类型变量test时,编译报错:
cannot use test (type Test1) as type Tester in argument to TestInterface:Test1 does not implement Tester (Display method has pointer receiver)
意思是说test并没有实现Tester的Display接口,因为golang中, 类型T只有接受者是T的方法,语法中T能直接调*T的方法仅仅是语法糖;而类型*T拥有接受者是T和*T的方法 。
1.3 类型判断
传入参数接口的时候,如果我们希望确切知道它具体类型,那么就要用到类型判断了。有两种类型判断方法:
变量.(类型) //判断是不是某个具体类型 switch 变量.(type) //返回具体类型,必须搭配swith语句
talk is cheap,看代码:
package main
import (
"fmt"
)
type Tester interface {
Display()
DisplayAppend(string)
DisplayAppend2(string) string
}
type Tester2 interface {
DisplayAppend(string)
}
type Test1 struct {
s string
}
func (t *Test1) Display() {
fmt.Printf("Display:%p, %#v\n", t ,t)
}
func (t Test1) DisplayAppend(s string) {
t.s += s
fmt.Printf("DisplayAppend:%p, %#v\n", &t, t)
}
func (t *Test1) DisplayAppend2 (s string) string {
t.s += s
fmt.Printf("DisplayAppend2:%p, %#v\n", t, t)
return t.s
}
func TestInterface(t Tester) {
t.Display()
t.DisplayAppend(" TestInterface")
t.DisplayAppend2(" TestInterface")
}
func TestInterface2(t Tester2) {
t.DisplayAppend("TestInterface2")
}
func Printf(t interface{}) {
if v, ok := t.(int); ok {
v = 2
fmt.Printf("type[%T] %v %v\n", v, v, t)
}
if v, ok := t.(int32);ok {
fmt.Printf("type[%T] %v\n", v, v)
}
if v, ok := t.(int64); ok {
fmt.Printf("type[%T] %v\n", v, v)
}
if v, ok := t.(Tester2); ok {
fmt.Printf("type[%T] %v\n", v, v)
}
if v, ok := t.(Tester); ok {
fmt.Printf("type[%T] %v\n", v, v)
}
}
func Printf2(v interface{}) {
fmt.Printf("%p %v\n", &v, v)
switch v := v.(type) {
case nil:
fmt.Printf("type[%T] %v\n", v, v)
case int:
fmt.Printf("%p %v\n", &v, v)
fmt.Printf("type[%T] %v\n", v, v)
case int64:
fmt.Printf("type[%T] %v\n", v, v)
case string:
fmt.Printf("type[%T] %v\n", v, v)
case Tester:
fmt.Printf("tester type[%T] %v\n", v, v)
case Tester2:
fmt.Printf("tester2 type[%T] %v\n", v, v)
default:
fmt.Printf("unknow\n")
}
}
func main() {
var i int64 = 1
Printf(i)
var i2 int = 1
Printf(i2)
var test Test1
Printf(test)
Printf(&test)
fmt.Printf("------------\n")
Printf2(i2)
Printf2(test)
Printf2(&test)
}
输出
type[int64] 1
type[int] 2 1
type[main.Test1] {}
main.Test1] &{}
main.Test1] &{}------------
0xc42000e220 1
0xc4200160b8 1
type[int] 1
0xc42000e240 {}
tester2 type[main.Test1] {}
0xc42000e250 &{}
tester type[*main.Test1] &{}
从这里我们可以看出两个点:
- 对于判断接口,只要实现该接口就能匹配上
- 类型判断返回也是按值复制,修改返回的value,不影响原来的值
- 接口的匹配,是严格的匹配,并不是说接口1能转换成接口2他们就能匹配上
golang中,我们经常用类型判断来判断特定的错误。
1.4 接口的值
接口的值简单来说,是由两部分组成的,就是类型和数据,详细的组成会在下面的实现章节中说明。
那么判断两个接口是相等,就是看他们的这两部分是否相等;另外 类型和数据都为nil才代表接口是nil ,eg:
var a interface{}
var b interface{} = (*int)(nil)
fmt.Println(a == nil, b == nil) //true false
这点很重要,很多人在实现error接口的时候判断错误,下面我们看个例子:
type MyError struct{}
func (*MyError) Error() string {
return "my error"
}
func TestError(x int) (int, error) {
var err *MyError
if x < 0 {
err = new(MyError)
}
return x, err
}
func main() {
var err error
_, err = TestError(10)
fmt.Println(err == nil) //false
}
在x大于0时,TestError中的err是nil,返回的时候,转换成error类型,那么类型就是(*MyError),值是nil,由于类型不是nil,所以最终返回给调用方的总是失败。
2 实现
如前面所说,golang中你不需要声明一个类型实现了那些接口,这带来了方便,但是实现上会比那些需要声明的语言更加复杂。golang的接口检测既有静态部分,也有动态部分。
-
静态部分
对于 具体类型(concrete type,包括自定义类型) -> interface,编译器生成对应的itab放到ELF的.rodata段,后续要获取itab时,直接把指针指向存在.rodata的相关偏移地址即可 。具体实现可以看golang的提交日志CL 20901、CL 20902。
对于 interface->具体类型(concrete type,包括自定义类型),编译器提取相关字段进行比较,并生成值
-
动态部分
在runtime中会有一个全局的hash表,记录了相应type->interface类型转换的itab,进行转换时候,先到hash表中查,如果有就返回成功;如果没有,就检查这两种类型能否转换,能就插入到hash表中返回成功,不能就返回失败。注意这里的hash表不是 go 中的map,而是一个最原始的使用数组的hash表,使用开放地址法来解决冲突。主要是 interface <-> interface(接口赋值给接口、接口转换成另一接口)使用到动态生产itab 。
2.1 结构
interface结构图
2.1.1 接口类型的结构 interfacetype
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
// imethod represents a method on an interface type
type imethod struct {
name nameOff // name of method
typ typeOff // .(*FuncType) underneath
}
pkgpath记录定义接口的包名
其中的mdhr字段,是一个imethod切片,记录接口中定义的那些函数。
nameOff 和 typeOff 类型是 int32 ,这两个值是链接器负责嵌入的,相对于可执行文件的元信息的偏移量。元信息会在运行期,加载到 runtime.moduledata 结构体中。
2.1.2 接口值的结构 iface eface
为了性能,golang专门分了两种interface,eface和iface,eface就是空接口,iface就是有方法的接口
结构定义分别在两个文件中,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.
}
data字段是用来存储实际数据的, runtime会申请一块新的内存,把数据考到那,然后data指向这块新的内存 。
eface、itab的_tpye字段,是data指向的值的实际类型信息。
iface中tab字段,是一个itab结构,包含了inter接口类型、_type数据类型、hash哈希的方法、fun函数地址占位符。这个hash方法拷贝自_type.hash;fun是一个大小为1的uintptr数组, 当fun[0]为0时,说明_type并没有实现该接口,当有实现接口时,fun存放了第一个接口方法的地址,其他方法一次往下存放,这里就简单用空间换时间,其实方法都在_type字段中能找到,实际在这记录下,每次调用的时候就不用动态查找了 。
2.1.2 全局的itab table
iface.go:
const itabInitSize = 512
// Note: change the formula in the mallocgc call in itabAdd if you change these fields.
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
}
可以看出这个全局的itabTable是用数组在存储的
size记录数组的大小,总是2的次幂。
count记录数组中已使用了多少。
entries是一个*itab数组,初始大小是512.
2.2 转换
把一个具体的值,赋值给接口,会调用conv系列函数,例如空接口调用convT2E系列、非空接口调用convT2I系列,为了性能考虑,很多特例的convT2I64、convT2Estring诸如此类,避免了typedmemmove的调用。
func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
}
if msanenabled {
msanread(elem, t.size)
}
x := mallocgc(t.size, t, true)
// TODO: We allocate a zeroed object only to overwrite it with actual data.
// Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.
typedmemmove(t, x, elem)
e._type = t
e.data = x
return
}
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I))
}
if msanenabled {
msanread(elem, t.size)
}
x := mallocgc(t.size, t, true)
typedmemmove(t, x, elem)
i.tab = tab
i.data = x
return
}
func convT2I16(tab *itab, val uint16) (i iface) {
t := tab._type
var x unsafe.Pointer
if val == 0 {
x = unsafe.Pointer(&zeroVal[0])
} else {
x = mallocgc(2, t, false)
*(*uint16)(x) = val
}
i.tab = tab
i.data = x
return
}
func convI2I(inter *interfacetype, i iface) (r iface) {
tab := i.tab
if tab == nil {
return
}
if tab.inter == inter {
r.tab = tab
r.data = i.data
return
}
r.tab = getitab(inter, tab._type, false)
r.data = i.data
return
}
可以看到:
- 具体类型转空接口,_type字段直接复制源的type;mallocgc一个新内存,把值复制过去,data再指向这块内存。
- 具体类型转非空接口,入参tab是编译器生成的填进去的,接口指向同一个入参tab指向的itab ;mallocgc一个新内存,把值复制过去,data再指向这块内存。
- 对于 接口转接口,itab是调用getitab函数去获取的 ,而不是编译器传入的。
对于那些特定类型的值,如果是零值,那么不会mallocgc一块新内存,data会指向zeroVal[0]。
2.2.1 编译器优化
每次都malloc一块内存,那么性能会很差,因此,对于一些类型,golang的编译器做了优化。 TODO
2.3 获取itab的流程
golang interface的核心逻辑就在这, 在get的时候,不仅仅会从itabTalbe中查找,还可能会创建插入,itabTable使用容量超过75%还会扩容 。下面我们看下代码:
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
if len(inter.mhdr) == 0 {
throw("internal error - misuse of itab")
}
// easy case
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
// First, look in the existing table to see if we can find the itab we need.
// This is by far the most common case, so do it without locks.
// Use atomic to ensure we see any previous writes done by the thread
// that updates the itabTable field (with atomic.Storep in itabAdd).
t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
if m = t.find(inter, typ); m != nil {
goto finish
}
// Not found. Grab the lock and try again.
lock(&itabLock)
if m = itabTable.find(inter, typ); m != nil {
unlock(&itabLock)
goto finish
}
// Entry doesn't exist yet. Make a new entry & add it.
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
m.init()
itabAdd(m)
unlock(&itabLock)
finish:
if m.fun[0] != 0 {
return m
}
if canfail {
return nil
}
// this can only happen if the conversion
// was already done once using the , ok form
// and we have a cached negative result.
// The cached result doesn't record which
// interface function was missing, so initialize
// the itab again to get the missing function name.
panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}
流程很简单
- 先用t保存全局itabTable的地址,然后使用t.find去查找,这样是为了防止查找过程中,itabTable被替换导致查找错误。
- 如果没找到,那么就会上锁,然后使用itabTable.find去查找,这样是因为在第一步查找的同时,另外一个协程写入,可能导致实际存在却查找不到,这时上锁避免itabTable被替换,然后直接在itaTable中查找。
- 再没找到,说明确实没有,那么就根据接口类型、数据类型,去生成一个新的itab,然后插入到itabTable中,这里可能会导致hash表扩容,如果数据类型并没有实现接口,那么根据调用方式,该报错报错,该panic panic。
这里我们可以看到申请新的itab空间时,内存空间的大小是unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize,参照前面接受的结构,len(inter.mhdr)就是接口定义的方法数量,因为字段fun是一个大小为1的数组,所以len(inter.mhdr)-1,在fun字段下面其实隐藏了其他方法接口地址。
然后我们再看下上面用到的一些方法的细节
2.3.1 在itabTable中查找itab find
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
// compiler has provided some good hash codes for us.
return uintptr(inter.typ.hash ^ typ.hash)
}
// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
// Implemented using quadratic probing.
// Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
// We're guaranteed to hit all table entries using this probe sequence.
mask := t.size - 1
h := itabHashFunc(inter, typ) & mask
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
// Use atomic read here so if we see m != nil, we also see
// the initializations of the fields of 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
h &= mask
}
}
从注释我们可以看到,golang使用的开放地址探测法,用的是公式h(i) = h0 + i*(i+1)/2 mod 2^k,h0是根据接口类型和数据类型的hash字段算出来的。以前的版本是额外使用一个link字段去连到下一个slot,那样会有额外的存储,性能也会差写,在1.11中我们看到做了改进,具体是哪个版本开始变的我也不知道。
2.3.2 检查并生成itab init
// 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++ {
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 ""
}
这个方法会检查interface和type的方法是否匹配,即type有没有实现interface。假如 interface有n中方法,type有m中方法,那么匹配的时间复杂度是O(n x m),由于interface、type的方法都按字典序排,所以O(n+m)的时间复杂度可以匹配完 。在检测的过程中,匹配上了,依次往fun字段写入type中对应方法的地址。如果 有一个方法没有匹配上,那么就设置fun[0]为0,在外层调用会检查fun[0]==0,即type并没有实现interface 。
这里我们还可以看到golang中continue的特殊用法, 要直接continue到外层的循环中,那么就在那一层的循环上加个标签,然后continue 标签 。
2.3.3 把itab插入到itabTable中 itabAdd
// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
func itabAdd(m *itab) {
// Bugs can lead to calling this while mallocing is set,
// typically because this is called while panicing.
// Crash reliably, rather than only when we need to grow
// the hash table.
if getg().m.mallocing != 0 {
throw("malloc deadlock")
}
t := itabTable
if t.count >= 3*(t.size/4) { // 75% load factor
// Grow hash table.
// t2 = new(itabTableType) + some additional entries
// We lie and tell malloc we want pointer-free memory because
// all the pointed-to values are not in the heap.
t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
t2.size = t.size * 2
// Copy over entries.
// Note: while copying, other threads may look for an itab and
// fail to find it. That's ok, they will then try to get the itab lock
// and as a consequence wait until this copying is complete.
iterate_itabs(t2.add)
if t2.count != t.count {
throw("mismatched count during itab table copy")
}
// Publish new hash table. Use an atomic write: see comment in getitab.
atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
// Adopt the new table as our own.
t = itabTable
// Note: the old table can be GC'ed here.
}
t.add(m)
}
// add adds the given itab to itab table t.
// itabLock must be held.
func (t *itabTableType) add(m *itab) {
// See comment in find about the probe sequence.
// Insert new itab in the first empty spot in the probe sequence.
mask := t.size - 1
h := itabHashFunc(m.inter, m._type) & mask
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
m2 := *p
if m2 == m {
// A given itab may be used in more than one module
// and thanks to the way global symbol resolution works, the
// pointed-to itab may already have been inserted into the
// global 'hash'.
return
}
if m2 == nil {
// Use atomic write here so if a reader sees m, it also
// sees the correctly initialized fields of m.
// NoWB is ok because m is not in heap memory.
// *p = m
atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
t.count++
return
}
h += I
h &= mask
}
}
可以看到,当hash表使用达到75%或以上时,就会进行扩容,容量是原来的2倍,申请完空间,就会把老表中的数据插入到新的hash表中。然后使itabTable指向新的表,最后把新的itab插入到新表中。
2.4 类型判断
2.4.1 接口转接口
func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {
tab := i.tab
if tab == nil {
return
}
if tab.inter != inter {
tab = getitab(inter, tab._type, true)
if tab == nil {
return
}
}
r.tab = tab
r.data = i.data
b = true
return
}
func assertE2I(inter *interfacetype, e eface) (r iface) {
t := e._type
if t == nil {
// explicit conversions require non-nil interface value.
panic(&TypeAssertionError{nil, nil, &inter.typ, ""})
}
r.tab = getitab(inter, t, false)
r.data = e.data
return
}
func assertE2I2(inter *interfacetype, e eface) (r iface, b bool) {
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
}
首先我们看到有两种用法:
- 返回值是一个时,不能转换就panic。
- 返回值是两个时,第二个返回值标记能否转换成功
此外,data复制的是指针,不会完整拷贝值
2.4.2 接口转具体类型
接口判断是否转换成具体类型,是编译器生成好的代码去做的。我们看个empty interface转换成具体类型的例子:
var EFace interface{}
var j int
func F4(i int) int{
EFace = I
j = EFace.(int)
return j
}
func main() {
F4(10)
}
我们反汇编看一下
go build -gcflags '-N -l' -o tmp build.go
go tool objdump -s "main.F4" tmp
可以看到汇编代码中有这么一段,我加注释你们就懂了:
MOVQ main.EFace(SB), CX //CX = EFace.typ LEAQ type.*+60128(SB), DX //DX = &type.int CMPQ DX, CX. //if DX == AX
可以看到 empty interface转具体类型,是编译器生成好对比代码,比较具体类型和空接口是不是同一个type,而不是调用某个函数在运行时动态对比 。
然后我们再看下非空接口类型转换:
var tf Tester
var t testStruct
func F4() int{
t := tf.(testStruct)
return t.i
}
func main() {
F4()
}
继续反汇编看一下:
MOVQ main.tf(SB), CX // CX = tf.tab(.inter.typ) LEAQ go.itab.main.testStruct,main.Tester(SB), DX // DX = <testStruct,Tester>对应的&itab(.inter.typ) CMPQ DX, CX //
可以看到, 非空接口转具体类型,也是编译器生成的代码,比较是不是同一个itab,而不是调用某个函数在运行时动态对比 。
最后
我还没确定golang程序启动时,是否会把编译期生成的itab插入到全局的hash表中?
还有赋值给interface时,编译优化避免malloc,这里我也不太懂
谁知道可以告诉一下我。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
ANSI Common Lisp
Paul Graham / Prentice Hall / 1995-11-12 / USD 116.40
For use as a core text supplement in any course covering common LISP such as Artificial Intelligence or Concepts of Programming Languages. Teaching students new and more powerful ways of thinking abo......一起来看看 《ANSI Common Lisp》 这本书的介绍吧!