Highlights of the GO language interview—what is the construction process of an interface?

We have already looked at the source code of ifaceand eface, and we know that ifacethe most important ones are itaband _type.

In order to study clearly how the interface is constructed, next I will pick up the weapon of assembly and restore the truth behind it.

Let’s look at a sample code:

package main

import "fmt"

type Person interface {
	growUp()
}

type Student struct {
	age int
}

func (p Student) growUp() {
	p.age += 1
	return
}

func main() {
	var qcrao = Person(Student{age: 18})

	fmt.Println(qcrao)
}

Excuting an order:

go tool compile -S main.go

The assembly code of the main function is as follows:

0x0000 00000 (./src/main.go:30) TEXT    "".main(SB), $80-0
0x0000 00000 (./src/main.go:30) MOVQ    (TLS), CX
0x0009 00009 (./src/main.go:30) CMPQ    SP, 16(CX)
0x000d 00013 (./src/main.go:30) JLS     157
0x0013 00019 (./src/main.go:30) SUBQ    $80, SP
0x0017 00023 (./src/main.go:30) MOVQ    BP, 72(SP)
0x001c 00028 (./src/main.go:30) LEAQ    72(SP), BP
0x0021 00033 (./src/main.go:30) FUNCDATA$0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0021 00033 (./src/main.go:30) FUNCDATA$1, gclocals·e226d4ae4a7cad8835311c6a4683c14f(SB)
0x0021 00033 (./src/main.go:31) MOVQ    $18, ""..autotmp_1+48(SP)
0x002a 00042 (./src/main.go:31) LEAQ    go.itab."".Student,"".Person(SB), AX
0x0031 00049 (./src/main.go:31) MOVQ    AX, (SP)
0x0035 00053 (./src/main.go:31) LEAQ    ""..autotmp_1+48(SP), AX
0x003a 00058 (./src/main.go:31) MOVQ    AX, 8(SP)
0x003f 00063 (./src/main.go:31) PCDATA  $0, $0
0x003f 00063 (./src/main.go:31) CALL    runtime.convT2I64(SB)
0x0044 00068 (./src/main.go:31) MOVQ    24(SP), AX
0x0049 00073 (./src/main.go:31) MOVQ    16(SP), CX
0x004e 00078 (./src/main.go:33) TESTQ   CX, CX
0x0051 00081 (./src/main.go:33) JEQ     87
0x0053 00083 (./src/main.go:33) MOVQ    8(CX), CX
0x0057 00087 (./src/main.go:33) MOVQ    $0, ""..autotmp_2+56(SP)
0x0060 00096 (./src/main.go:33) MOVQ    $0, ""..autotmp_2+64(SP)
0x0069 00105 (./src/main.go:33) MOVQ    CX, ""..autotmp_2+56(SP)
0x006e 00110 (./src/main.go:33) MOVQ    AX, ""..autotmp_2+64(SP)
0x0073 00115 (./src/main.go:33) LEAQ    ""..autotmp_2+56(SP), AX
0x0078 00120 (./src/main.go:33) MOVQ    AX, (SP)
0x007c 00124 (./src/main.go:33) MOVQ    $1, 8(SP)
0x0085 00133 (./src/main.go:33) MOVQ    $1, 16(SP)
0x008e 00142 (./src/main.go:33) PCDATA  $0, $1
0x008e 00142 (./src/main.go:33) CALL    fmt.Println(SB)
0x0093 00147 (./src/main.go:34) MOVQ    72(SP), BP
0x0098 00152 (./src/main.go:34) ADDQ    $80, SP
0x009c 00156 (./src/main.go:34) RET
0x009d 00157 (./src/main.go:34) NOP
0x009d 00157 (./src/main.go:30) PCDATA  $0, $-1
0x009d 00157 (./src/main.go:30) CALL    runtime.morestack_noctxt(SB)
0x00a2 00162 (./src/main.go:30) JMP     0

Let’s start from line 10. If you don’t understand the previous lines of assembly code, you can go back and read the previous two articles on the official account, which I will omit here.

number of assembly lines operate
10-14 runtime.convT2I64(SB)Parameters for the constructor call

Let’s take a look at the parameter form of this function:

func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {
	// ……
}

convT2I64One will be constructed inteface, which is our Personinterface.

The location of the first parameter is (SP)assigned go.itab."".Student,"".Person(SB)the address of .

From the generated assembly we find:

go.itab."".Student,"".Person SNOPTRDATA dupok size=40
        0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  
        0x0010 00 00 00 00 00 00 00 00 da 9f 20 d4              
        rel 0+8 t=1 type."".Person+0
        rel 8+8 t=1 type."".Student+0

size=40The size is 40 bytes, to recap:

type itab struct {
	inter  *interfacetype // 8字节
	_type  *_type // 8字节
	link   *itab // 8字节
	hash   uint32 // 4字节
	bad    bool   // 1字节
	inhash bool   // 1字节
	unused [2]byte // 2字节
	fun    [1]uintptr // variable sized // 8字节
}

Adding up the sizes of each field, itabthe size of the structure is 40 bytes. The string of numbers above is actually itabthe serialized content. Notice that most of the numbers are 0, and the 4 bytes starting from 24 bytes da 9f 20 d4are actually the value itabof hash, which is used when judging whether two types are the same arrive.

The following two lines are link instructions. Simply put, they combine all source files and assign a global position value to each symbol. The meaning here is also relatively clear: the first 8 bytes ultimately store the type."".Personaddress of , corresponding to the field itabin inter, indicating the interface type; the 8-16 bytes ultimately store type."".Studentthe address of , corresponding to the field itabin _type, indicating the specific type.

The second parameter is relatively simple. It is 18the address of the number, which is also Studentused when initializing the structure.

number of assembly lines operate
15 transferruntime.convT2I64(SB)

Take a look at the code specifically:

func convT2I64(tab *itab, elem unsafe.Pointer) (i iface) {
	t := tab._type
	
	//...
	
	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
}

This piece of code is relatively simple, assigning to tabthe field ifaceof tab; datapart of it is to apply for a piece of memory on the heap, and then copy the elempointed to. 18This ifaceis it.

number of assembly lines operate
17 assign i.tabtoCX
18 assign i.datatoAX
19-21 Check i.tabwhether is nil. If not, move CX by 8 bytes, that is, assign the field itabof _typeCX to CX. This is also the entity type of the interface and will eventually be used as fmt.Printlna parameter of the function.

Later, there is the calling fmt.Printlnfunction and the previous parameter preparation work, so I won’t go into details.

In this way, we have interfacefinished the construction process of a .

[Extension 1]
How to print out Hashthe value of the interface type?

Here is a reference to an article translated by Cao Dashen, which will be written in the reference materials. The specific steps are as follows:

type iface struct {
	tab  *itab
	data unsafe.Pointer
}
type itab struct {
	inter uintptr
	_type uintptr
	link uintptr
	hash  uint32
	_     [4]byte
	fun   [1]uintptr
}

func main() {
	var qcrao = Person(Student{age: 18})

	iface := (*iface)(unsafe.Pointer(&qcrao))
	fmt.Printf("iface.tab.hash = %#x\n", iface.tab.hash)
}

Define a 山寨版and iface, itabsaying it 山寨is because itabsome key data structures in are not expanded specifically. For example _type, you can find out by comparing the authentic definitions, but 山寨版it can still work, because _typeis just a pointer.

In mainthe function, first construct an interface object qcrao, then force type conversion, and finally read hashthe value, which is very good! You can also try it yourself.

operation result:

iface.tab.hash = 0xd4209fda

It is worth mentioning that qcraowhen constructing the interface, even if I agewrite it as other values, the obtained hashvalue will still remain unchanged. This should be expected. hashThe value is only related to its fields and methods.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132795742