Go语言之再谈空接口

开发 后端
Go编译器可以把任何类型的数据封装成该结构的对象;结果是在语法层面, interface{} 类型的变量可以引用任何类型的数据, interface{} 类型的形式参数可以接收任何类型的实际参数。

[[430589]]

前言

在 【Go】内存中的空接口 一文中,我们知道 interface{} 类型的对象的结构如下:

  1. // emptyInterface is the header for an interface{} value. 
  2. type emptyInterface struct { 
  3.   typ  *rtype 
  4.   word unsafe.Pointer 

该结构体包含两个指针,占 16 个字节。

该结构体包含类型信息和数据信息,Go编译器可以把任何类型的数据封装成该结构的对象;结果是在语法层面, interface{} 类型的变量可以引用任何类型的数据, interface{} 类型的形式参数可以接收任何类型的实际参数。

前文只是介绍了 interface{} 的对象结构,但还没有介绍 interface{} 的类型信息。本文将详细分析空接口的类型信息。

继续阅读文本之前,强烈推荐优先阅读以下两篇图文:

  • 【Go】内存中的空接口
  • 【Go】深入理解函数

环境

  1. OS : Ubuntu 20.04.2 LTS; x86_64 
  2. Go : go version go1.16.2 linux/amd64 

声明

操作系统、处理器架构、Go版本不同,均有可能造成相同的源码编译后运行时的寄存器值、内存地址、数据结构等存在差异。

本文仅包含 64 位系统架构下的 64 位可执行程序的研究分析。

本文仅保证学习过程中的分析数据在当前环境下的准确有效性。

代码清单

  1. package main 
  2.  
  3. import "fmt" 
  4. import "reflect" 
  5.  
  6. func main() { 
  7.   Typeof(Typeof) 
  8.  
  9. //go:noinline 
  10. func Typeof(i interface{}) { 
  11.   t := reflect.TypeOf(i) 
  12.   fmt.Println("函数类型", t.String()) 
  13.   fmt.Println("参数类型", t.In(0).String()) 

运行结果

接口类型

接口类型信息的结构定义在reflect/type.go源文件中:

  1. // interfaceType represents an interface type. 
  2. type interfaceType struct { 
  3.   rtype 
  4.   pkgPath name      // import path 
  5.   methods []imethod // sorted by hash 

该结构体占 80 个字节。

结构分布图

将接口类型信息绘制成图表如下:

内存分析

再次强调,继续之前务必阅读 【Go】内存中的函数 ,方便了解本文分析过程。

定义Typeof函数的目的是方便定位 interface{} 的类型信息,直接在该函数入口处设置断点。

在调试过程中,首先得到的是Typeof函数的类型信息。该函数只有一个参数,参数的类型信息位于内存地址 0x4a5200 处,这就是空接口的类型信息。

将空接口的类型信息做成图表如下:

  • rtype.size = 16
  • rtype.ptrdata = 16
  • rtype.hash = 0x18a057e7
  • rtype.tflag = 2 = reflect.tflagExtraStar
  • rtype.align = 8
  • rtype.fieldAlign = 8
  • rtype.kind = 20 = reflect.Interface
  • rtype.equal = 0x4c2ba8 = runtime.nilinterequal
  • rtype.str = 0x000030b0 => *interface {}
  • rtype.ptrToThis = 0x000067c0
  • interfaceType.pkgPath = 0 = nil
  • interfaceType.methods.Data = 0x4a5220
  • interfaceType.methods.Len = 0
  • interfaceType.methods.Cap = 0

从以上数据我们可以知道,interface{} 类型的对象 16 字节大小、8 字节对齐、具有可比较性,没有任何方法。

ptrToThis = 0x000067c0 是空接口指针类型(*interface{})信息的内存偏移量。

空接口是Go语言内置的数据类型,不属于任何包,所以包路径值为空(pkgPath)。

可比较性

空接口具有可比较性。

空接口类型的equal函数为runtime.nilinterequal,该函数定义在runtime/alg.go源文件中:

  1. func nilinterequal(p, q unsafe.Pointer) bool { 
  2.   x := *(*eface)(p) 
  3.   y := *(*eface)(q) 
  4.   return x._type == y._type && efaceeq(x._type, x.data, y.data) 
  5.  
  6. func efaceeq(t *_type, x, y unsafe.Pointer) bool { 
  7.   if t == nil { 
  8.     return true 
  9.   } 
  10.   eq := t.equal 
  11.   if eq == nil { 
  12.     panic(errorString("comparing uncomparable type " + t.string())) 
  13.   } 
  14.   if isDirectIface(t) { 
  15.     return x == y 
  16.   } 
  17.   return eq(x, y) 
  18. // isDirectIface reports whether t is stored directly in an interface value. 
  19. func isDirectIface(t *_type) bool { 
  20.   return t.kind&kindDirectIface != 0 

通过源码,看到空接口对象比较的逻辑如下:

  1. 如果两个对象的类型不一样,返回 false
  2. 如果两个对象的类型一样,并且值为nil,返回 true
  3. 如果两个对象的类型一样,但是不可比较,直接 panic
  4. 然后进行比较,并返回比较结果。

也就是说,空接口对象的比较,实际是其表示的类型和其指向的数据的比较。

通过内存中的空接口和本文,基本已经全面了解了 interface{}。

本文转载自微信公众号「Golang In Memory」

 

责任编辑:姜华 来源: Golang In Memory
相关推荐

2021-10-03 22:18:14

Go语言整数

2020-12-31 09:06:44

Go语言Reflect

2021-10-16 17:53:35

Go函数编程

2022-03-28 13:34:26

Go泛型部署泛型

2021-10-09 07:52:01

Go程序重命名

2019-01-03 09:45:20

Go 前端 Web

2024-01-05 20:46:14

2013-08-20 10:11:20

Go系统管理员

2021-05-12 08:53:54

Go语言调度

2021-10-18 10:53:26

Go 代码技术

2024-01-08 08:23:07

Go语言代码

2013-07-10 11:11:05

PythonGo语言

2012-08-13 14:13:46

2018-08-01 15:10:02

GolangPython语言

2012-02-13 10:03:31

编程开发

2017-06-14 09:37:05

R语言Apriori算法

2018-03-12 22:13:46

GO语言编程软件

2013-02-19 09:26:17

2010-01-14 10:34:02

C++语言

2022-08-15 23:09:53

jsonGo语言
点赞
收藏

51CTO技术栈公众号