当前位置: 首页 > ops >正文

深入 Go 底层原理(十三):interface 的内部表示与动态派发

1. 引言

接口(interface)是 Go 语言实现多态和代码解耦的核心。一个变量如果实现了接口要求的所有方法,我们就可以说它“是”这个接口类型。这种动态的类型行为背后,是一套清晰而高效的内存布局和派发机制。

本文将深入 runtime,揭示 Go interface 的两种内部表示 efaceiface,并解释方法调用(动态派发)是如何实现的。

2. interface 的两种内部表示

interface 在 Go 的底层有两种不同的结构体表示,取决于接口的类型。

a) eface (Empty Interface)

用于表示空接口 interface{}。任何类型都可以赋值给空接口。

// src/runtime/runtime2.go
type eface struct {_type *_type // 指向变量的动态类型信息data  unsafe.Pointer // 指向变量的实际数据
}
  • _type: 是一个 runtime._type 结构体指针,包含了关于这个变量的所有类型信息(如类型名称、大小、哈希值等)。

  • data: 是一个指针,指向被存入接口的实际数据的副本。

当执行 var i interface{} = "hello" 时,i 在内存中就是一个 eface,其 _type 指向 string 的类型信息,data 指向字符串 "hello" 的数据。

b) iface (Interface with Methods)

用于表示带有方法的接口,例如 io.Reader

// src/runtime/runtime2.go
type iface struct {tab  *itab          // 接口方法表指针data unsafe.Pointer // 指向变量的实际数据
}
  • data: 与 eface 相同,指向实际数据。

  • tab (itab): 这是实现动态派发的关键。itab (interface table) 是一个结构体,包含了:

    • inter: 指向接口类型的定义。

    • _type: 指向具体类型(动态类型)的定义。

    • fun: 一个函数指针数组。这个数组的长度等于接口定义的方法数量。数组中的每个指针都指向具体类型所实现的对应方法。

当执行 var r io.Reader = os.File{} 时,Go runtime 会在内部构建一个 itab。这个 itab 会确认 os.File 类型确实实现了 io.Reader 的所有方法(如 Read()),然后将 os.FileRead 方法的函数地址存入 itabfun 数组中。最后,用这个 itab 和指向 os.File 数据的指针来填充 iface

3. 动态派发 (Dynamic Dispatch)

当我们通过一个接口变量调用方法时,例如 r.Read(...),其执行流程如下:

  1. iface 中取出 tab (itab) 指针。

  2. itabfun 数组中,根据方法在接口定义中的顺序,找到对应的函数指针。例如,Readio.Reader 的第一个方法,就取 fun[0]

  3. iface 中取出 data 指针(即 receiver)。

  4. data 作为第一个参数(receiver),调用获取到的函数指针。

这个通过 itab 查找并调用方法的过程,就叫做动态派发。因为具体调用哪个函数是在运行时才决定的,所以会比直接调用(静态派发)有微小的性能开销。

4. 类型断言 (value, ok := i.(T))

类型断言的实现也依赖于 efaceiface

  • 对于 i.(T)runtime 会取出 i 内部的 _type (来自 efaceiface.tab._type),并将其与 T 的类型信息进行比较。

  • 如果类型完全匹配,断言成功,oktruevalue 被赋予 data 指针指向的数据。

  • 如果不匹配,断言失败,okfalsevalueT 的零值。如果是不带 ok 的断言,则会直接 panic

http://www.xdnf.cn/news/16842.html

相关文章:

  • Redisson高并发实战:Netty IO线程免遭阻塞的守护指南
  • 算法提升之数学(快速幂+逆元求法)
  • 【20min 急速入门】使用Demucs进行音轨分离
  • Redis7 String类型数据
  • 【iOS】KVO
  • MyBatisPlus之CRUD接口(IService与BaseMapper)
  • 28Rsync免密传输与定时备份
  • 关于Web前端安全防御XSS攻防的几点考虑
  • Spring Boot 全 YAML 配置 Liquibase 教程
  • C++之vector类的代码及其逻辑详解 (中)
  • DockerFile文件执行docker bulid自动构建镜像
  • CMake指令:mark_as_advanced
  • Python序列去重高级指南:保持顺序的高效去重技术
  • 错误: 找不到或无法加载主类 原因: java.lang.ClassNotFoundException
  • 云原生三剑客:Kubernetes + Docker + Spring Cloud 实战指南与深度整合
  • 分类任务当中常见指标 F1分数、recall、准确率分别是什么含义
  • 类似 Pixso 但更侧重「网页 / 软件界面设计」「前后端可视化开发」的工具
  • 【贪心】P11112 [ROI 2024] 机器人物流 (Day 1)|普及+
  • 基于python多光谱遥感数据处理、图像分类、定量评估及机器学习方法应用
  • Java函数式编程之【Stream终止操作】【下】【二】【收集器toMap()】【叁参数收集操作collect()】
  • Maven项目和Spring项目的异同
  • 企业资产|企业资产管理系统|基于springboot企业资产管理系统设计与实现(源码+数据库+文档)
  • Docker容器中文PDF生成解决方案
  • 计算机网络:为什么IPv6没有选择使用点分十进制
  • Pytorch-02数据集和数据加载器的基本原理和基本操作
  • Matplotlib - Python图表可视化利器
  • 面试小总结
  • vue引入阿里巴巴矢量图库的方式
  • 内网穿透系列十:高性能内网穿透工具 rathole,支持Docker一键部署
  • ubuntu 系统风扇控制软件 CoolerControl