Aspectos destacados de la entrevista sobre el idioma GO: ¿cuál es el proceso de construcción de una interfaz?

Ya hemos visto el código fuente de ifacey efacey sabemos que ifacelos más importantes son itaby _type.

Para estudiar claramente cómo se construye la interfaz, a continuación tomaré el arma del ensamblaje y restauraré la verdad detrás de ella.

Veamos un código de muestra:

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)
}

Ejecutando una orden:

go tool compile -S main.go

El código ensamblador de la función principal es el siguiente:

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

Comencemos desde la línea 10. Si no comprende las líneas anteriores del código ensamblador, puede regresar y leer los dos artículos anteriores en la cuenta oficial, que omitiré aquí.

número de líneas de montaje funcionar
10-14 runtime.convT2I64(SB)Parámetros para la llamada al constructor.

Echemos un vistazo a la forma de parámetro de esta función:

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

convT2I64Se construirá uno inteface, que es nuestra Personinterfaz.

A la ubicación del primer parámetro se (SP)le asigna go.itab."".Student,"".Person(SB)la dirección de .

Del ensamblaje generado encontramos:

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=40El tamaño es de 40 bytes, para resumir:

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字节
}

Sumando los tamaños de cada campo, itabel tamaño de la estructura es de 40 bytes. La cadena de números anterior es en realidad itabel contenido serializado. Tenga en cuenta que la mayoría de los números son 0 y los 4 bytes que comienzan con 24 bytes da 9f 20 d4son en realidad el valor itabde hash, que se utiliza para juzgar si llegan dos tipos iguales.

Las siguientes dos líneas son instrucciones de enlace: en pocas palabras, combinan todos los archivos fuente y asignan un valor de posición global a cada símbolo. El significado aquí también es relativamente claro: los primeros 8 bytes finalmente almacenan la type."".Persondirección de, correspondiente al campo itaben inter, que indica el tipo de interfaz; los 8-16 bytes finalmente almacenan type."".Studentla dirección de, correspondiente al campo itaben _type, que indica el tipo específico .

El segundo parámetro es relativamente simple: es 18la dirección del número, que también se Studentutiliza al inicializar la estructura.

número de líneas de montaje funcionar
15 transferirruntime.convT2I64(SB)

Eche un vistazo al código específicamente:

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
}

Este fragmento de código es relativamente simple: se asigna al tabcampo ifacede tab; dataparte de él es solicitar un fragmento de memoria en el montón y luego copiar el elemseñalado . 18Esto ifacees todo.

número de líneas de montaje funcionar
17 asignar i.tabaCX
18 asignar i.dataaAX
19-21 Compruebe i.tabsi es nulo. Si no, mueva CX 8 bytes, es decir, asigne el campo itabde _typeCX a CX. Este también es el tipo de entidad de la interfaz y eventualmente se usará como fmt.Printlnparámetro de la función.

Más adelante, está la fmt.Printlnfunción de llamada y el trabajo previo de preparación de parámetros, por lo que no entraré en detalles.

De esta forma hemos interfacefinalizado el proceso de construcción de un .

[Extensión 1]
¿Cómo imprimir Hashel valor del tipo de interfaz?

Aquí hay una referencia a un artículo traducido por Cao Dashen, que se escribirá en los materiales de referencia. Los pasos específicos son los siguientes:

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)
}

Defina a 山寨版y iface, itabdiciendo que 山寨es porque itabalgunas estructuras de datos clave no se expanden específicamente. Por ejemplo _type, puede averiguarlo comparando las definiciones auténticas, pero 山寨版aún puede funcionar, porque _typees solo un puntero.

En mainla función, primero construya un objeto de interfaz qcrao, luego fuerce la conversión de tipo y finalmente lea hashel valor, ¡lo cual es muy bueno! También puedes probarlo tú mismo.

resultado de la operación:

iface.tab.hash = 0xd4209fda

Vale la pena mencionar que qcraoal construir la interfaz, incluso si agela escribo como otros valores, el valor obtenido hashseguirá sin cambios. Esto es de esperarse. hashEl valor sólo está relacionado con sus campos y métodos.

Supongo que te gusta

Origin blog.csdn.net/zy_dreamer/article/details/132795742
Recomendado
Clasificación