理论验证
下面我们还是通过测试代码来验证我们的理论,我们自己定义底层的相关类型,然后通过强制类型转换,来尝试解析interface变量中的数据:
type Iface struct { Tab *Itab Data unsafe.Pointer } type Itab struct { Inter uintptr Type uintptr Hash uint32 _ [4]byte Fun [1]uintptr } type Eface struct { Type uintptr Data unsafe.Pointer } func TestInterface(t *testing.T) { var ( p Point nilP interface{} ) point := &point3d{X: 1, Y: 2, Z: 3} nilP = point fmt.Printf("eface size:%v\n", unsafe.Sizeof(nilP)) eface := (*face.Eface)(unsafe.Pointer(&nilP)) spew.Dump(eface.Type) spew.Dump(eface.Data) fmt.Printf("eface offset: eface._type = %v, eface.data = %v\n\n", unsafe.Offsetof(eface.Type), unsafe.Offsetof(eface.Data)) p = point fmt.Printf("point size:%v\n", unsafe.Sizeof(p)) iface := (*face.Iface)(unsafe.Pointer(&p)) spew.Dump(iface.Tab) spew.Dump(iface.Data) fmt.Printf("Iface offset: iface.tab = %v, iface.data = %v\n\n", unsafe.Offsetof(iface.Tab), unsafe.Offsetof(iface.Data)) }
执行程序输出为:
$ go test -v -run TestInterface === RUN TestInterface eface size:16 (uintptr) 0x111f2c0 (unsafe.Pointer) 0xc00008e250 eface offset: eface._type = 0, eface.data = 8 point size:16 (*face.Itab)(0x116ec40)({ Inter: (uintptr) 0x1122680, Type: (uintptr) 0x111f2c0, Hash: (uint32) 960374823, _: ([4]uint8) (len=4 cap=4) { 00000000 00 00 00 00 |....| }, Fun: ([1]uintptr) (len=1 cap=1) { (uintptr) 0x10fce20 } }) (unsafe.Pointer) 0xc00008e250 Iface offset: iface.tab = 0, iface.data = 8
下面我们再通过汇编代码看下,赋值操作做了什么?
type Point interface { Println() } type point3d struct { X, Y, Z float32 } func (p *point3d) Println() { fmt.Printf("%v,%v,%v\n", p.X, p.Y, p.Z) } func main() { point := point3d{X: 1, Y: 2, Z: 3} // L18 var ( nilP interface{} // L20 p Point // L21 ) nilP = &point // L23 p = &point // L24 fmt.Println(nilP, p) }
通过 go tool 查看汇编代码如下:
TEXT main.main(SB) /Users/cyningsun/Documents/go/src/github.com/cyningsun/go-test/20200102-inside-golang-object-model/main/build.go build.go:17 0x1094de0 65488b0c2530000000 MOVQ GS:0x30, CX build.go:17 0x1094de9 488d4424b0 LEAQ -0x50(SP), AX build.go:17 0x1094dee 483b4110 CMPQ 0x10(CX), AX build.go:17 0x1094df2 0f86b9010000 JBE 0x1094fb1 build.go:17 0x1094df8 4881ecd0000000 SUBQ $0xd0, SP build.go:17 0x1094dff 4889ac24c8000000 MOVQ BP, 0xc8(SP) build.go:17 0x1094e07 488dac24c8000000 LEAQ 0xc8(SP), BP build.go:18 0x1094e0f 488d05ea1e0200 LEAQ type.*+137216(SB), AX // point := point3d{X: 1, Y: 2, Z: 3} build.go:18 0x1094e16 48890424 MOVQ AX, 0(SP) build.go:18 0x1094e1a e81160f7ff CALL runtime.newobject(SB) build.go:18 0x1094e1f 488b442408 MOVQ 0x8(SP), AX build.go:18 0x1094e24 4889442458 MOVQ AX, 0x58(SP) build.go:18 0x1094e29 0f57c0 XORPS X0, X0 build.go:18 0x1094e2c f30f11442434 MOVSS X0, 0x34(SP) build.go:18 0x1094e32 f30f11442438 MOVSS X0, 0x38(SP) build.go:18 0x1094e38 f30f1144243c MOVSS X0, 0x3c(SP) build.go:18 0x1094e3e f30f1005a6b80400 MOVSS $f32.3f800000(SB), X0 build.go:18 0x1094e46 f30f11442434 MOVSS X0, 0x34(SP) build.go:18 0x1094e4c f30f100d9cb80400 MOVSS $f32.40000000(SB), X1 build.go:18 0x1094e54 f30f114c2438 MOVSS X1, 0x38(SP) build.go:18 0x1094e5a f30f101592b80400 MOVSS $f32.40400000(SB), X2 build.go:18 0x1094e62 f30f1154243c MOVSS X2, 0x3c(SP) build.go:18 0x1094e68 488b442458 MOVQ 0x58(SP), AX build.go:18 0x1094e6d f30f1100 MOVSS X0, 0(AX) build.go:18 0x1094e71 f30f114804 MOVSS X1, 0x4(AX) build.go:18 0x1094e76 f30f115008 MOVSS X2, 0x8(AX) build.go:20 0x1094e7b 0f57c0 XORPS X0, X0 // nilP interface{} build.go:20 0x1094e7e 0f11442470 MOVUPS X0, 0x70(SP)// nilP 开始地址为0x70 build.go:21 0x1094e83 0f57c0 XORPS X0, X0 // p Point build.go:21 0x1094e86 0f11442460 MOVUPS X0, 0x60(SP) build.go:23 0x1094e8b 488b442458 MOVQ 0x58(SP), AX // nilP = &point ;0x58(SP) 为 point 的地址 build.go:23 0x1094e90 4889442448 MOVQ AX, 0x48(SP) // SP 指向 point 地址 build.go:23 0x1094e95 488d0da4860100 LEAQ type.*+98368(SB), CX // ;从内存加载 Point类型地址 到 CX 寄存器 build.go:23 0x1094e9c 48894c2470 MOVQ CX, 0x70(SP) // ;将 Point类型地址(8字节) 保存到 0x70(即eface._type) build.go:23 0x1094ea1 4889442478 MOVQ AX, 0x78(SP) // ;将 point 对象地址(8字节) 保存到 0x78(即eface.data) build.go:24 0x1094ea6 488b442458 MOVQ 0x58(SP), AX // p = &point build.go:24 0x1094eab 4889442448 MOVQ AX, 0x48(SP) // ;SP 指向 point 地址 build.go:24 0x1094eb0 488d0d09d50400 LEAQ go.itab.*main.point3d,main.Point(SB), CX // ;从内存加载 Point类型 itab 地址 到 CX 寄存器 build.go:24 0x1094eb7 48894c2460 MOVQ CX, 0x60(SP) // ;将 Point类型地址(8字节) 保存到 0x70(即iface.tab) build.go:24 0x1094ebc 4889442468 MOVQ AX, 0x68(SP) // ;将 point 对象地址(8字节) 保存到 0x78(即iface.data) build.go:25 0x1094ec1 488b442468 MOVQ 0x68(SP), AX // fmt.Println(nilP, p) ...
事实正如理论一般,在编译
阶段,赋值命令被转化为类型信息和对象指针的拷贝,保存下来执行期转换所需要的一切信息。
综述
从底层代码和汇编出发,分析 struct 和 interface 的 对象模型,理清了Go 语言高级特性的底层机制。再去学习反射等表层细节,事半功倍。
参考链接:
- https://www.cnblogs.com/qcrao-2018/p/11124360.html
- https://yougg.github.io/2017/03/27/理解go语言模型1interface底层详解/
- https://wudaijun.com/2018/01/go-interface-implement/
- https://chai2010.cn/advanced-go-programming-book/ch3-asm/readme.html
- https://www.cnblogs.com/lsgxeva/p/8948153.html
源代码:https://github.com/cyningsun/go-test
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/01-12-2020/inside-the-go-object-model.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!