gccgo如何实现golang运行时向特定interface的动态conversion(及和C++虚函数表的对比)

news/2025/10/29 21:25:13/文章来源:https://www.cnblogs.com/tsecer/p/19175446

gccgo如何实现golang运行时向特定interface的动态conversion(及和C++虚函数表的对比)

intro

在阅读k8s的源代码时,发现代码中有一个(一组)很简单粗暴的interface转换:将一个interface(storage对象)尝试向各种类型转换。golang中每个结构的method并不是在结构内定义,在类型转换的时候,运行时只要判断转换源实现了目的类型的所有method就可以成功完成转换。

k8s的例子里,总体来说是对一个struct所有可能实现的method都逐个尝试判断是否有对应实现。在这里的典型场景中,这个storage对象可能的确是实现了所有的interface(rest.Creater, rest.Lister, etc...)。

在之前的这篇文章,说明了gcc的对于interface的转换,本质上也是返回一个包含函数指针的数组(类似于C++的虚函数表。注意:函数表中函数的位置顺序要和目标interface的顺序一致)。

但是和C++需函数定义在一个类内部不同,一个golang struct如果实现了n个method,那么所有组合可能的不同interface类型数量就有(n + 1)!个那么多。面对如此众多的“可能的”interface,golang不可能和C++一样,在编译时为所有类都生成一个徐函数表。

那么:golang如何应对这么多中可能的转换呢?

///@file: kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {admit := a.group.Admit
///...// what verbs are supported by the storage, used to know what verbs we support per pathcreater, isCreater := storage.(rest.Creater)namedCreater, isNamedCreater := storage.(rest.NamedCreater)lister, isLister := storage.(rest.Lister)getter, isGetter := storage.(rest.Getter)getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)updater, isUpdater := storage.(rest.Updater)patcher, isPatcher := storage.(rest.Patcher)watcher, isWatcher := storage.(rest.Watcher)connecter, isConnecter := storage.(rest.Connecter)storageMeta, isMetadata := storage.(rest.StorageMetadata)storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)gvAcceptor, _ := storage.(rest.GroupVersionAcceptor)
///...
}

测试

下面是在compiler explorer上的简单测试代码,就是单纯的做一下接口转换:

/// https://godbolt.org/z/x9Ts8v458

// Type your code here, or load an example.
package p
type IF interface {get() intset() int
}
type ANY interface {}func square(x ANY) int {y := x.(IF)return y.get()
}

生成gimple代码,关键的接口类型转换调用的是runtime.assertitab,函数调用传入的两个参数是编译时可以确定的目标interface类型(go.p,IF..d)和转换源对象自带的类型描述符(x.__type_descriptor;)。

p.square (struct ANY x)
{int D.417;int $ret0;$ret0 = 0;{struct IF y;try{_1 = x.__type_descriptor;_2 = runtime.assertitab (&go.p.IF..d, _1);y.__methods = _2;_3 = x.__object;y.__object = _3;{_4 = y.__methods;_5 = _4->get;_6 = y.__object;GOTMP.0 = _5 (_6);$ret0 = GOTMP.0;D.417 = $ret0;return D.417;}}finally{y = {CLOBBER};}}
}

assertitab

gcc包含的golang运行时代码assertitab=>>getitab==>>init也比较简单,就是拿两个类型表进行类型比较,在运行时根据转换目标需要的method来填充定制的函数表。

///@file: /home/tsecer/source/gcc/libgo/go/runtime/iface.go
// init fills in the m.methods array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.methods[1] to nil and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {inter := m.intertyp := m._type()ni := len(inter.methods) + 1methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.methods[0]))[:ni:ni]var m1 unsafe.Pointerri := 0for li := range inter.methods {lhsMethod := &inter.methods[li]var rhsMethod *methodfor {if ri >= len(typ.methods) {m.methods[1] = nilreturn *lhsMethod.name}rhsMethod = &typ.methods[ri]if (lhsMethod.name == rhsMethod.name || *lhsMethod.name == *rhsMethod.name) &&(lhsMethod.pkgPath == rhsMethod.pkgPath || *lhsMethod.pkgPath == *rhsMethod.pkgPath) {break}ri++}if !eqtype(lhsMethod.typ, rhsMethod.mtyp) {m.methods[1] = nilreturn *lhsMethod.name}if li == 0 {m1 = rhsMethod.tfn // we'll set m.methods[1] at the end} else {methods[li+1] = rhsMethod.tfn}ri++}m.methods[1] = m1return ""
}

在函数表生成之后还会将新生成的接口表(itab)缓存起来,从而避免下次转换时再重新生成。

///@file: /home/tsecer/source/gcc/libgo/go/runtime/iface.go
// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
func itabAdd(m *itab) {// Bugs can lead to calling this while mallocing is set,// typically because this is called while panicing.// Crash reliably, rather than only when we need to grow// the hash table.if getg().m.mallocing != 0 {throw("malloc deadlock")}t := itabTableif t.count >= 3*(t.size/4) { // 75% load factor// Grow hash table.// t2 = new(itabTableType) + some additional entries// We lie and tell malloc we want pointer-free memory because// all the pointed-to values are not in the heap.t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true))t2.size = t.size * 2// Copy over entries.// Note: while copying, other threads may look for an itab and// fail to find it. That's ok, they will then try to get the itab lock// and as a consequence wait until this copying is complete.iterate_itabs(t2.add)if t2.count != t.count {throw("mismatched count during itab table copy")}// Publish new hash table. Use an atomic write: see comment in getitab.atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))// Adopt the new table as our own.t = itabTable// Note: the old table can be GC'ed here.}t.add(m)
}

outro

如果从C++的角度来看golang把method散落在结构之外的写法有些诡异,但是从这个例子可以看到:这种实现配合上golang的反射机制,也可以实现跟C++相比更加灵活一些的接口转换机制。尽管是在C++的基础上增加了运行时性能为代价,但在一些性能不是最核心需求的长江下,这点“牺牲”是可以接受的。

基于这个机制可以想到的一个重要机制就是“解耦”:一个结构只要定义了某个interface的所有method,它就能在“运行时”向这个interface 作类型转换,运行时会动态创建这个接口需要的“函数表”; 并且一个结构可以转换的目标类型,也不仅仅必须是它的基类,从而将不同类型的关系从“继承”关系中解耦出来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/950267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

技术人的公关利器:专业新闻稿撰写AI指令深度解析

以前我也是这样,每次写新闻稿都像在写一个没有需求文档的项目,各种要素和结构都要自己摸索。直到我尝试用AI提示词工程的思路来解决这个问题。作为程序员,我们习惯用代码解决技术问题,但面对企业公关、产品发布时却…

2025年最新考勤门禁系统推荐与选型攻略

本文将深度对比8款考勤门禁系统:i人事、 北森、柠檬智联、HID Global、真地(Realand)、Honeywell、TimeClock Plus、ACTi。在企业数字化管理的浪潮中,「考勤门禁系统」已成为人力资源与行政管理的关键基础设施。无…

2026 NOI 做题记录(八)

推荐阅读:A、E、J、K、O、R、S、U、W、ADContest Link \(\text{By DaiRuiChen007}\)*A. [AGC071D] Level K Terms (8) Problem Link 我们声称序列合法当且仅当如下两个条件均满足:定义 \(z_i=\begin{cases}i&i&l…

代码审查API

from fastapi import FastAPI, HTTPException, Depends from pydantic import BaseModel, Field from sqlmodel import SQLModel, Field as SQLField, Session, create_engine, select from typing import List, Optio…

elk架构安装部署

一、elk架构概述概述 ELK是一套开源免费且功能强大的日志分析管理系统,由Elasticsearch、Logstash、Kibana三部分组成,简称ELK ELK可以将系统日志、网站日志、应用系统日志等各种日志进行收集、过滤、清洗,然后进行…

冒泡排序 试做版 2025/10/29 21:13

include <stdio.h> int main() { int n,i,k,p; while(scanf("%d",&n)==1) //读入数组个数n { int arr[n]; //创建数组 该数组刚好能容纳n组数据 for(i=0;i<n;i++) //从数组0开始 数组0 数组…

CSP 45^2复赛游记

DAY -300+ 好激动好激动好激动这是肝硬化学了半年的 \(OI\) ,终于迎来了此生以来第一次考吃薯片-J,去的燕大,然后就是成功翘掉一天课好高兴好高兴!带了 \(inf\) 个零食,包括但不限于:蒟蒻果冻(绿色的,好像是青…

工厂用什么考勤系统好?2025最新8款推荐

本文将深度对比8款工厂考勤系统:i人事、 北森、盖雅实时考勤云、TimeClock Plus、全易通考勤系统、ADP Time & Attendance、同鑫考勤系统、When I Work。在制造业与工业企业中,工厂考勤系统早已成为提升管理效率…

深度技术解析低功耗蓝牙厂商nordic的nRF Connect SDK裸机选项方案

自2018年以来,Nordic Semiconductor一直致力于开发基于Zephyr实时操作系统( RTOS)的nRF Connect SDK,并不断发展,提供最佳的工具与软件,助力开发者在Nordic广泛的无线产品组合上构建高度可扩展的物联网应用。然而,…

MPK(Mirage Persistent Kernel)源码笔记(3)--- 系统接口

MPK(Mirage Persistent Kernel)源码笔记(3)--- 系统接口 目录MPK(Mirage Persistent Kernel)源码笔记(3)--- 系统接口0x00 概述0x01 流程0x02 初始化0x03 定义计算图0x04 编译0x05 执行0xFF 参考 0x00 概述 因…

硬件基础知识和典型应用-在休眠项目中RS485电路休眠设计

硬件基础知识和典型应用-在休眠项目中RS485电路休眠设计<p><iframe name="ifd" src="https://mnifdv.cn/resource/cnblogs/LearnHardware" frameborder="0" scrolling="a…

20232317 2025-2026-1 《网络与系统攻防技术》实验三实验报告

一、实验内容 (1)正确使用msf编码器,veil-evasion,自己利用shellcode编程等免杀工具或技巧。 1.正确使用msf编码器,使用msfvenom生成如jar之类的其他文件 2.veil,加壳工具 3.使用C + shellcode编程 (2)通过组合应用…

用 Gemini + VS Code 打造属于你的 AI 编程神器(完胜 Cursor!)

大家好! 最近很多朋友问我,Cursor + Claude 是不是最强 AI 编程组合? 确实,现在主流的 AI 编程软件中,Cursor 加 Claude Cloud 可以说是“王者中的王炸”。 但问题也很现实——太贵了! 对于刚入门的开发者、小白…

《程序员修炼之道:从小工到专家》观后感第三篇

笔记三:《应对变化——正交性与模块化的灵活设计思维》 核心观点:“正交性”设计能最大化降低代码耦合度,模块化是应对需求变化的“缓冲带”。在业务需求频繁迭代的场景中,正交且模块化的系统能实现“局部修改、全…

profile 与 profile.d 在 Linux 发行版本中的作用 - ENGINEER

profile 与 profile.d 在 Linux 发行版本中的作用​ ​核心概念与作用​​​/etc/profile​:系统范围的登录 Shell 初始化脚本,登录时由 Shell 自动执行,负责设置全局环境变量(如 ​PATH、USER、LOGNAME、HOME、MA…

思维day1

思维day1P6005 [USACO20JAN] Time is Mooney G Bessie 正在安排前往牛尼亚的一次出差,那里有 N(2≤N≤1000)个编号为 1…N 的城市,由 M(1≤M≤2000)条单向的道路连接。Bessie 每次访问城市 i 都可以赚到\(m_{i}\…

内存本地修改

ctrl + esc 可以查看浏览器进程 然后通过CE进行修改内存

Nordic NRF54第四代蓝牙产品最优赋能---三星SmartThings Find设备追踪服务

全球低功耗无线通信半导体解决方案领导者 Nordic Semiconductor(以下简称 “Nordic”) 的下一代 nRF54L15和nRF54L10 无线 SoC 以及广为业界采用的 nRF52840和 nRF52833SoC 现已支持三星 SmartThings Find SDK。 此软件…

求 max(区间不同数的个数-区间mex)

rt 做法:先枚举\(mex\),说明\(mex\)不会在答案区间中存在 那么答案区间就是一个不包含\(mex\)的极长区间,可能有\(n\)个 一个区间中不同数的个数可以用树状数组求:维护以下标为权值的桶,每次更新last数组const in…

《程序员修炼之道:从小工到专家》前五分之三观后感

读《程序员修炼之道:从小工到专家》前五分之三内容,最深刻的感受是它不仅是技术指南,更是程序员的思维重塑手册。书中“务实的哲学”“注重基础”“迭代改进”等理念,像一面镜子照见日常开发中的不足,也指明了从小…