Golang中小值类型的接口实现与应用

Golang中小值类型的接口实现与应用 大家好,

以为在我刚开始学习 Go 接口的工作原理时,在某个地方看到过:如果值足够小,能够放入 interface{} 的第二个 uintptr 中,那么值本身会被放入该位置,而不是创建一个指向该值的指针并将指针放入该位置。例如,我以为这段代码:

n := int32(1)
var i interface{} = n
data := *((*[2]uintptr)(unsafe.Pointer(&i)))
fmt.Println(data)

会输出:

[975264 1]

但我实际看到的是:

[975264 272826108]

其中第二个数字是指向实际值为 1 的 int32 的指针。

我现在正在谷歌搜索,想找到最初读到这个说法的地方,但还没找到。我的问题不是“为什么”,因为我观察到的行为与当接口中的值是无法放入 uintptr 的较大结构体时我所预期的一致。我的问题是:“我是不是完全搞错了,以为小值会被直接放入 interface{} 中?一直都是这样的吗?” 另外,“我这样测试是否正确?”

编辑 1:Russ Cox 在 2009 年 12 月 1 日关于 Go 接口的页面:https://research.swtch.com/interfaces


更多关于Golang中小值类型的接口实现与应用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

谢谢,雅各布,我会留意看看是否能找到原因。

更多关于Golang中小值类型的接口实现与应用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


过去的情况确实如你所说。但现在已不再是这样。我不完全记得变更的具体原因,但隐约记得这与垃圾回收机制实现完全精确化有关。

// 代码示例保留原文
func example() {
    // 代码内容不翻译
}

在 Go 语言中,接口值的内部表示确实包含一个用于存储数据的字段,但小值并不会直接内联存储到接口的第二个 uintptr 中。接口值由两部分组成:类型信息和数据指针。对于小值(如 int32),Go 会创建一个该值的副本,并将指向该副本的指针存储在接口的数据字段中。你观察到的行为是正确的,第二个数字确实是指向存储值 1int32 副本的指针。

你的测试方法是正确的,它展示了接口的内部结构。以下是一个更详细的示例,演示了接口值的内部表示:

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	n := int32(1)
	var i interface{} = n
	data := *((*[2]uintptr)(unsafe.Pointer(&i)))
	fmt.Printf("Interface data: %v\n", data)

	// 提取类型指针和数据指针
	typePtr := data[0]
	dataPtr := data[1]

	fmt.Printf("Type pointer: %v\n", typePtr)
	fmt.Printf("Data pointer: %v\n", dataPtr)

	// 通过数据指针访问实际值
	value := *(*int32)(unsafe.Pointer(dataPtr))
	fmt.Printf("Value through data pointer: %d\n", value)
}

输出示例(具体数字可能因运行环境而异):

Interface data: [975264 272826108]
Type pointer: 975264
Data pointer: 272826108
Value through data pointer: 1

这个行为在 Go 的早期版本中就已经存在,并且是设计的一部分。Russ Cox 在 2009 年的文章《Go 接口》中描述了接口的基本结构,但并未提到小值的内联优化。接口的数据字段始终存储指针,指向值的副本或原始值(如果是指针类型)。

回到顶部