Correlation data structure
1. Interface type – iface
typeUnsafe. // unsafe. // unsafe. // unsafe. // unsafe. // unsafe. // unsafe. // unsafe. // unsafe. // unsafe. //Copy the code
Obviously, the interface’s data store only needs to be 2 Pointers in size.
2. Interface Type – ITab
typeItab struct {inter * interfaceType // Int *int *int *int *int *int *int *inthash 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.
}
Copy the code
Interface type information Mapping information recorded from an object to an interface. For a given interface and a given type, only one copy of this information exists. The member Fun is a list of function entry addresses for the interface, and on 64-bit compilers, fun has an offset of exactly 0x18 (8 + 8 + 4 + 4 = 16).
Interface Implementation Principle
- The assignment of an interface is simply a filling of the iFace structure. Where the iface. TAB value comes from will be described later in the source code.
- Iface.data.fun [index] for a given interface, the index of the function in itAB is determined by the number of fun (index).
Source code analysis
1. Source test code
import "fmt"
type car interface {
run3()
run4()
run5()
}
type car2 interface {
run3()
run5()
}
type smallCar struct {
}
func (self *smallCar)run1() {
fmt.Println("s run1")
}
func (self *smallCar)run2() {
fmt.Println("s run2")
}
func (self *smallCar)run3() {
fmt.Println("s run3")
}
func (self *smallCar)run4() {
fmt.Println("s run4")
}
func (self *smallCar)run5() {
fmt.Println("s run5")
}
func main() {
var c car
var c2 car2
var sco smallCar
sco.run3()
c = &sco
c.run3()
c2 = c
c2.run3()
}
Copy the code
2. assembly code
main.go:38 0x4af359 488b8900000000 mov rcx, qword ptr [rcx] main.go:38 0x4af360 483b6110 cmp rsp, qword ptr [rcx+0x10] main.go:38 0x4af364 0f86c5000000 jbe 0x4af42f => main.go:38 0x4af36a* 4883ec68 sub rsp, 0x68 main.go:38 0x4af36e 48896c2460 mov qword ptr [rsp+0x60], rbp main.go:38 0x4af373 488d6c2460 lea rbp, ptr [rsp+0x60] main.go:39 0x4af378 0f57c0 xorps xmm0, xmm0 main.go:39 0x4af37b 0f11442450 movups xmmword ptr [rsp+0x50], xmm0 main.go:40 0x4af380 0f57c0 xorps xmm0, xmm0 main.go:40 0x4af383 0f11442440 movups xmmword ptr [rsp+0x40], xmm0 main.go:41 0x4af388 488d0591aa0100 lea rax, ptr [__image_base__+826912] main.go:41 0x4af38f 48890424 mov qword ptr [rsp], rax main.go:41 0x4af393 e898cdf5ff call $runtime.newobject main.go:41 0x4af398 488b442408 mov rax, Go :41 0x4af39d 4889442438 mov qword PTR [RSP +0x38], Rax main.go:43 0x4af3A2 4889442428 mov qword PTR [RSP +0x28], rax Run3 main.go:43 0x4af3a7 48890424 mov qword PTR [RSP], smallCar.run3 main.go:43 0x4af3a7 48890424 mov rax main.go:43 0x4af3ab e8c0fdffff call $main.(*smallCar).run3 main.go:45 0x4af3b0 488b442438 mov rax, qword ptr [rsp+0x38] main.go:45 0x4af3b5 4889442430 mov qword ptr [rsp+0x30], Rax // "c = &sco" main.go:45 0x4AF3BA 488d0D3F650400 LEA RCX, PTR [__image_base__+1005824] //(__image_base__+1005824) Is the mapping information of type smallCar to interface CAR main.go:45 0x4AF3C1 48894c2450 MOV qword ptr [rsp+0x50], rcx main.go:45 0x4af3c6 4889442458 mov qword ptr [rsp+0x58], Rax // The code corresponding to the following fragment is "c.run3()" main.go:46 0x4af3cb 488b442450 mov rax, qword ptr [rsp+0x50] main.go:46 0x4af3d0 8400 test byte ptr [rax], al main.go:46 0x4af3d2 488b4018 mov rax, Qword PTR [rax+0x18] qword PTR [rax+0x18] qword PTR [rax+0x18] qword ptr [rsp+0x58] main.go:46 0x4af3db 48890c24 mov qword ptr [rsp], RCX main.go:46 0x4af3df ffd0 call rax // The following code is equivalent to pseudocode // c2 = Runtime. convI2I(car2). C) main.go:48 0x4AF3E1 488D0538C50100 Lea Rax, PTR [__image_base__+833824] //(__image_base__+833824) is the interface type information of CAR2, Go :48 0x4af3E8 48890424 mov qword PTR [RSP], rax main.go:48 0x4af3EC 488b442458 mov rax, qword ptr [rsp+0x58] main.go:48 0x4af3f1 488b4c2450 mov rcx, qword ptr [rsp+0x50] main.go:48 0x4af3f6 48894c2408 mov qword ptr [rsp+0x8], rcx main.go:48 0x4af3fb 4889442410 mov qword ptr [rsp+0x10], rax main.go:48 0x4af400 e8bba3f5ff call $runtime.convI2I main.go:48 0x4af405 488b442418 mov rax, qword ptr [rsp+0x18] main.go:48 0x4af40a 488b4c2420 mov rcx, Qword PTR [RSP +0x20] // "c2.run3()" main.go:48 0x4af40f 4889442440 mov qword PTR [RSP +0x40], rax main.go:48 0x4af414 48894c2448 mov qword ptr [rsp+0x48], rcx main.go:49 0x4af419 8400 test byte ptr [rax], al main.go:49 0x4af41b 488b4018 mov rax, qword ptr [rax+0x18] main.go:49 0x4af41f 48890c24 mov qword ptr [rsp], rcx main.go:49 0x4af423 ffd0 call rax main.go:50 0x4af425 488b6c2460 mov rbp, qword ptr [rsp+0x60] main.go:50 0x4af42a 4883c468 add rsp, 0x68 main.go:50 0x4af42e c3 ret main.go:38 0x4af42f e88c6afaff call $runtime.morestack_noctxt main.go:38 0x4af434 e917ffffff jmp $main.mainCopy the code
3. Code parsing
Some key points are already commented in the assembly code
- The source code “c = &sco” actually shows that smallCar to CAR mapping information (i.e., c. tabb) is hardcoded and generated at compile time (this information is present in the global data area).
- ConvI2I = runtime.convi2i = runtime.convi2i = runtime.convi2i = runtime.convi2i = runtime.convi2i = runtime.convi2i = runtime.convi2i = runtime.convi2i = runtime.convi2i
4. The runtime. ConvI2I parsing
4.1 Runtime. convI2I Call Process:
runtime.convI2I -> runtime.getitab
4.2 Runtime. convI2I functions:
- _type Is used to query the mapping between smallCar and Car2 in the Runtime. itabTable hash table based on the real type of C. tab.
- C. tabb is generated using smallCar and CAR2 types and stored in Runtime. itabTable.
After debugging, it was found that the first conversion from CAR to CAR2 needed to generate C. tab. since the actual type of car was not known at compile time when car was converted to CAR2, there was no conversion from smallCar to CAR2, so it needed to be constructed at run time
Summary and comparison
- Golang interface is flexible
Golang interface is loose, meaning that there is no explicit inheritance relationship between the type and interface. It is behavior-determined interface that looks like a cat is a cat, and looks like a dog is a dog. Interfaces such as C++ are characterized by genealogy-determined interfaces where the father is a cat, and even if a subclass looks like a dog, it can only be a cat.
- Too much on the back of simplicity
A. In fact, if we only look at the conversion process of the object to the interface, the runtime is not special, similar to C++ based on the polymorphic implementation of the VPTR function table, the difference is that golang does not have this VPTR table, the interface has this VPTR table. B. However, conversion between golang interfaces is troublesome. At least one hash table query is required to obtain the VPTR table of the object for the target interface. And language such as c + +, inheritance due to the specific type and makes for an inheritance chain of any type, the same virtual functions in different types of VPTR indexes in the table is the same, so you just need to each type has a VPTR table, can at any level of the called function interface, according to “take the object of VPTR table; Get the function address by function index; Call the function address “trilogy to implement an efficient runtime polymorphic scheme without Overload. C. Golang’s implementation of the interface provides an advantage — the ability to determine whether the conversion from interface to interface is legitimate. Because the essence of interface to interface conversion is the conversion process from the object pointing to the original interface to the target interface, and the interface carries enough type information. The reflection mechanism implemented through the type information can accurately know whether the object and interface match at run time. C++, without the use of RTTI, would require the programmer to know whether the source object is an instance of the target object when casting an object pointer. Otherwise, the behavior would be unknown.