go接口变量底层存的是两字宽结构体:具名接口为itab+data,interface{}为_type+data;nil接口要求两字段均为零值,故含非nil itab的接口即使data为nil也不等于nil。

Go 接口变量底层到底存了什么
Go 的接口变量不是简单指针,而是一个两字宽的结构体:一个指向类型信息的指针 + 一个指向数据的指针。对 interface{}(即 eface)来说,是 _type 和 data;对具名接口(如 io.Reader)来说,是 itab 和 data(itab 里已含 _type 和函数表)。你看到的 var r io.Reader = &bytes.Buffer{},背后不是直接存 &bytes.Buffer,而是先查 itab,再把地址塞进 data 字段。
为什么 nil 接口不等于 nil 指针
这是最常踩的坑:接口变量为 nil,要求 **两个字段都为零值**;而只要它装了一个非 nil 指针(哪怕该指针本身是 nil),itab 或 _type 就非零,整个接口就非 nil。
-
var r io.Reader→r == nil成立(itab==nil && data==nil) -
var b *bytes.Buffer; r = b→r == nil不成立(itab != nil,data == nil) - 典型报错:
panic: runtime error: invalid memory address or nil pointer dereference发生在你对后者调用r.Read(...)时
iface 和 eface 在汇编和反射中的可见性
它们是运行时内部结构,Go 源码里不可直接声明或访问,但可通过 unsafe + 反射窥探。比如用 reflect.ValueOf(x).UnsafeAddr() 获取接口头起始地址后,偏移 0 是 itab/_type,偏移 8(64 位)是 data。不过要注意:
-
eface(interface{})只有_type+data,没有方法表 -
iface(具名接口)的itab是全局唯一缓存的,相同接口+类型组合只生成一次 - 用
unsafe.Sizeof测接口变量大小,永远是 16 字节(64 位系统),和底层具体类型无关
什么时候必须关心 iface 的内存布局
绝大多数业务代码完全不需要碰。真要介入的场景极少,典型如:
立即学习“go语言免费学习笔记(深入)”;
- 写高性能序列化库,想绕过反射直接解包接口头提取
data地址 - 调试 GC 问题,发现某接口变量长期持有一个大对象,但
data字段实际指向已释放内存(说明itab还活着,GC 误判) - 用
cgo传接口给 C,需手动构造iface结构体(极危险,不推荐)
别为了“理解原理”去强行操作——Go 把接口抽象得足够干净,暴露 iface 和 eface 本意是服务运行时,不是给你日常编码用的。










