Golang中nil接口的指针是什么

Golang中nil接口的指针是什么 大家好: 在阅读了一些关于Golang学习的书籍后,我总结出:当你声明某些引用类型时,“nil"表示"已声明但尚未分配任何内存”。因此当你打印这些类型零值的指针时,指针应该是0x0:

	var m1 map[int]string

	if m1 == nil {
		fmt.Printf("type of m1: %T \t addr of m1: %p \n", m1, m1)
	}

	var m2 []int

	if m2 == nil {
		fmt.Printf("type of m2: %T \t addr of m2: %p \n", m2, m2)
	}

	var m3 *int 

	if m3 == nil {
		fmt.Printf("type of m3: %T \t addr of m3: %p \n", m3, m3)
	}

	var m4 interface {
		foobar() int
	}

	if m4 == nil {
		fmt.Printf("type of m4: %T \t addr of m4: %p \n", m4, m4)
	}

	var m5 chan int

	if m5 == nil {
		fmt.Printf("type of m5: %T \t addr of m5: %p \n", m5, m5)
	}

	var m6 func(t string)
	if m6 == nil {
		fmt.Printf("type of m6: %T \t addr of m6: %p \n", m6, m6)
	}

	// output:
	// type of m1: map[int]string 	 addr of m1: 0x0 
	// type of m2: []int 	 addr of m2: 0x0 
	// type of m3: *int 	 addr of m3: 0x0 
	// type of m4: <nil> 	 addr of m4: %!p(<nil>) 
	// type of m5: chan int 	 addr of m5: 0x0 
	// type of m6: func(string) 	 addr of m6: 0x0 

如你所见,我已经测试了Golang中所有6种引用类型,其中5种类型支持我关于检查nil值的结论,但接口类型不支持。我无法打印它的指针。我该怎么做?或者为什么Golang阻止我打印它?


更多关于Golang中nil接口的指针是什么的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

从输出结果可以看出,m4 目前甚至还没有类型。您可以将接口类型视为第二级类型。接口在赋值后才会获得其类型。

也许可以通过 unsafe.Pointer 找到 var m4 的内存地址,但我从未尝试过!

func main() {
    fmt.Println("hello world")
}

更多关于Golang中nil接口的指针是什么的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,我意识到接口类型是一种非常特殊的类型,你的解释确实很有道理。谢谢! 我花了很多时间研究 nil 值,与其他语言中的类似类型相比,这确实让我感到困惑。很遗憾不能将"引用类型的 nil 值"描述为:已声明但尚未分配任何内存,只有一个例外,那就是接口类型。

// 代码示例保留原样

感谢回复。 现在我有两种方法来打印 m4 的内存地址:

	var m4 interface {
		foobar() int
	}

	if m4 == nil {
		fmt.Printf("way1: addr of m4: %p \n", unsafe.Pointer(&m4))
		fmt.Printf("way2: addr of m4: %v \n", reflect.ValueOf(&m4).Elem().Addr())
	}
//output
//way1: addr of m4: 0xc0000541c0 
//way2: addr of m4: 0xc0000541c0

它们都指向相同的地址,这是否意味着在声明接口类型时,Golang 会为其预分配内存?也许这是一个晦涩且无意义的问题,我只是对此感到好奇。

在编译过程中可能会分配一些内存,但我怀疑在运行时这些内存是否还存在,不过可能会保留一些类型信息(如果这些信息在某个地方被使用的话)。

接口类型用于保证值的行为。在编写代码时,我们通常不直接关注对象的值,而是关注它的某些行为。

例如,如果我们有一个Unix文件描述符 var int fd = 0,在这个STDIN的情况下,我们很少对输入通道的值 0 感兴趣,而是想要从中读取数据。

现在我们可以使用该值调用read系统调用并从通道中读取。

但实际上,我们也可以编写一个函数,从任何定义了 io.Reader 接口的对象中读取数据,甚至可以让 int 值成为 io.Reader

func (fd int) Read(buf []bytes) (n int, err error) {
    /* 一些实现细节 */
}

有了这个Read函数:

var x int
var y io.Reader

y = x

这样就有意义了,yx 实际上具有相同对象的类型和值,但存储在两个不同的变量中。

在你的示例中,&x&m4 的值只是显示了该值存储的位置。所以 &x&y 是不同的,因为我们有两个变量,但是 y = x 并且当然 y == x 也是成立的。

当然,编译器会在 y = x 时检查x是否实现了 io.Reader 接口,但它不会预先分配任何东西。在 y = x 之后,y 将具有与 x 相同的类型和值,但在被赋值之前,它既没有类型也没有值。

在Go语言中,接口类型的零值确实是nil,但接口的内部结构与其他引用类型不同。接口由两部分组成:动态类型和动态值。当接口为nil时,这两部分都为nil

使用%p格式化动词打印nil接口时会出现问题,因为%p期望一个指针类型的参数,而nil接口没有具体的底层指针地址可提供。

以下是正确的处理方式:

package main

import (
	"fmt"
	"unsafe"
)

type MyInterface interface {
	foobar() int
}

func main() {
	var m4 MyInterface

	if m4 == nil {
		fmt.Printf("type of m4: %T\n", m4)  // type of m4: <nil>
		fmt.Printf("m4 is nil: %v\n", m4 == nil)  // m4 is nil: true
		
		// 使用unsafe.Pointer查看接口的内部表示
		iface := (*[2]uintptr)(unsafe.Pointer(&m4))
		fmt.Printf("interface data: type=%v, value=%v\n", iface[0], iface[1])
		// interface data: type=0, value=0
	}

	// 如果需要获取接口变量的地址,可以使用&操作符
	fmt.Printf("address of m4 variable: %p\n", &m4)

	// 对比非nil接口的情况
	var x *int
	m4 = x
	if m4 == nil {
		fmt.Println("m4 with nil pointer is still nil")
	} else {
		fmt.Println("m4 with nil pointer is not nil")
	}
}

输出结果:

type of m4: <nil>
m4 is nil: true
interface data: type=0, value=0
address of m4 variable: 0xc000010230
m4 with nil pointer is still nil

关键点:

  1. nil接口的%T显示为<nil>
  2. 使用unsafe.Pointer可以查看接口的内部结构
  3. 接口变量本身的地址可以通过&m4获取
  4. 包含nil指针的接口不等于nil接口

Go阻止直接打印nil接口的指针是因为接口类型的特殊实现机制,%p格式化动词不适用于nil接口值。

回到顶部