GO 言語インタビューのハイライト — インターフェースの構築プロセスとは何ですか?

ifaceすでにと のソース コードを確認しましたが、最も重要なものはと であるefaceことがわかりますifaceitab_type

インターフェイスがどのように構築されているかを明確に研究するために、次にアセンブリという武器を手に取り、その背後にある真実を復元します。

サンプルコードを見てみましょう:

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

注文の実行:

go tool compile -S main.go

main関数のアセンブリコードは次のとおりです。

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

10 行目から始めましょう。アセンブリ コードの前の行が理解できない場合は、公式アカウントの前 2 つの記事に戻って読んでください。ここでは省略します。

組立ラインの数 操作する
10-14 コンストラクター呼び出しruntime.convT2I64(SB)のパラメーター

この関数のパラメータ形式を見てみましょう。

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

convT2I64inteface1 つはインターフェースであり、構築されますPerson

最初のパラメータの場所には、のアドレスが(SP)割り当てられます。go.itab."".Student,"".Person(SB)

生成されたアセンブリから次のことがわかります。

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=40要約すると、サイズは 40 バイトです。

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

各フィールドのサイズを合計すると、itab構造体のサイズは 40 バイトになります。上記の数字の列は、実際には、 arriv のitabシリアル化されたda 9f 20 d4コンテンツですitabhash

次の 2 行はリンク命令で、簡単に言うと、すべてのソース ファイルを結合し、グローバル位置の値を各シンボルに割り当てます。ここでの意味も比較的明確です。最初の 8 バイトは最終的に のアドレスを格納し、 のフィールドに対応し、インターフェイス タイプを示します。8 ~ 16 バイトは最終的に のアドレスを格納し、type."".Personフィールド対応し、特定のタイプを示します。 。itabintertype."".Studentitab_type

182 番目のパラメータは比較的単純で、数値のアドレスであり、Student構造体の初期化時にも使用されます。

組立ラインの数 操作する
15 移行runtime.convT2I64(SB)

具体的にコードを見てみましょう。

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
}

このコード部分は比較的単純で、 のフィールドに代入していますtabifaceそのtab一部dataは、ヒープ上のメモリの一部を適用し、elemポイントされた18ものをコピーすることです。これifaceです。

組立ラインの数 操作する
17 i.tab割り当てるCX
18 i.data割り当てるAX
19-21 nil かどうかを確認しi.tab、そうでない場合は CX を 8 バイト移動、つまりCXitabのフィールド_typeを CX に割り当てます。これはインターフェイスのエンティティ タイプでもあり、最終的にはfmt.Println関数のパラメーターとして使用されます。

この後、関数の呼び出しとその前のパラメータの準備作業があるfmt.Printlnため、詳細は説明しません。

このようにして、interfaceの構築プロセスが完了しました。

【拡張1】インターフェースタイプの値を
出力するにはどうすればいいですか?Hash

参考資料に記載する曹大深氏の翻訳記事を引用します。具体的な手順は次のとおりです。

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

の一部の主要なデータ構造が特に展開されていないためであるとしてa山寨版と を定義します。たとえば、 は本物の定義を比較することでわかりますが、は単なるポインタであるため、それでも機能します。ifaceitab山寨itab_type山寨版_type

main関数では、最初にインターフェイス オブジェクトを構築しqcrao、次に型変換を強制し、最後にhash値を読み取ります。これは非常に良いことです。自分で試してみることもできます。

操作結果:

iface.tab.hash = 0xd4209fda

注意すべき点は、qcraoインターフェイスを構築するときに、age他の値として記述しても、取得されたhash値は変更されないことです。これは想定内のことであり、hash値はそのフィールドとメソッドにのみ関連付けられます。

おすすめ

転載: blog.csdn.net/zy_dreamer/article/details/132795742