# 接口的构造过程是怎样的

我们已经看过了 `iface` 和 `eface` 的源码，知道 `iface` 最重要的是 `itab` 和 `_type`。

为了研究清楚接口是如何构造的，接下来我会拿起汇编的武器，还原背后的真相。

来看一个示例代码：

```go
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 行开始看，如果不理解前面几行汇编代码的话，可以回去看看公众号前面两篇文章，这里我就省略了。

| 汇编行数  | 操作                               |
| ----- | -------------------------------- |
| 10-14 | 构造调用 `runtime.convT2I64(SB)` 的参数 |

我们来看下这个函数的参数形式：

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

`convT2I64` 会构造出一个 `inteface`，也就是我们的 `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字节，回顾一下：

```go
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 字节。上面那一串数字实际上是 `itab` 序列化后的内容，注意到大部分数字是 0，从 24 字节开始的 4 个字节 `da 9f 20 d4` 实际上是 `itab` 的 `hash` 值，这在判断两个类型是否相同的时候会用到。

下面两行是链接指令，简单说就是将所有源文件综合起来，给每个符号赋予一个全局的位置值。这里的意思也比较明确：前8个字节最终存储的是 `type."".Person` 的地址，对应 `itab` 里的 `inter` 字段，表示接口类型；8-16 字节最终存储的是 `type."".Student` 的地址，对应 `itab` 里 `_type` 字段，表示具体类型。

第二个参数就比较简单了，它就是数字 `18` 的地址，这也是初始化 `Student` 结构体的时候会用到。

| 汇编行数 | 操作                         |
| ---- | -------------------------- |
| 15   | 调用 `runtime.convT2I64(SB)` |

具体看下代码：

```go
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
}
```

这块代码比较简单，把 `tab` 赋给了 `iface` 的 `tab` 字段；`data` 部分则是在堆上申请了一块内存，然后将 `elem` 指向的 `18` 拷贝过去。这样 `iface` 就组装好了。

| 汇编行数  | 操作                                                                                                          |
| ----- | ----------------------------------------------------------------------------------------------------------- |
| 17    | 把 `i.tab` 赋给 `CX`                                                                                           |
| 18    | 把 `i.data` 赋给 `AX`                                                                                          |
| 19-21 | 检测 `i.tab` 是否是 nil，如果不是的话，把 CX 移动 8 个字节，也就是把 `itab` 的 `_type` 字段赋给了 CX，这也是接口的实体类型，最终要作为 `fmt.Println` 函数的参数 |

后面，就是调用 `fmt.Println` 函数及之前的参数准备工作了，不再赘述。

这样，我们就把一个 `interface` 的构造过程说完了。

【引申1】 如何打印出接口类型的 `Hash` 值？

这里参考曹大神翻译的一篇文章，参考资料里会写上。具体做法如下：

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

定义了一个`山寨版`的 `iface` 和 `itab`，说它`山寨`是因为 `itab` 里的一些关键数据结构都不具体展开了，比如 `_type`，对比一下正宗的定义就可以发现，但是`山寨版`依然能工作，因为 `_type` 就是一个指针而已嘛。

在 `main` 函数里，先构造出一个接口对象 `qcrao`，然后强制类型转换，最后读取出 `hash` 值，非常妙！你也可以自己动手试一下。

运行结果：

```
iface.tab.hash = 0xd4209fda
```

值得一提的是，构造接口 `qcrao` 的时候，即使我把 `age` 写成其他值，得到的 `hash` 值依然不变的，这应该是可以预料的，`hash` 值只和他的字段、方法相关。

## 参考资料

【曹大神翻译的文章，非常硬核】<http://xargin.com/go-and-interface/#reconstructing-an-itab-from-an-executable>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://qcrao91.gitbook.io/go/interface/jie-kou-de-gou-zao-guo-cheng-shi-zen-yang-de.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
