golang map 变量保存的是实际值所在的地址吗

这是思否上我回答过的一个问题,大致是关于为什么 fmt.Printf 的 %p 打印的是 map 的地址。

题主原问题如下:

初始化了一个map型的变量m,使用printf(%p)的格式分别对m和&m进行输出,分别得到两个地址。

&m显而易见是m变量的地址,令我困惑的是m也可以输出一个地址,但有人说m保存的并非指针,那为什么m却可以输出地址?map类型的变量访问的机制是什么样的?

我是从源码角度回答了这个问题,原始回答如下:

要明白这个问题,追追源码了。

可以看下 map 的初始化函数 make 的源码。make 可用于 map、slice、chan 三种类型,每个类型都有相应的 make 实现,比如,map 的 make 实现在源码文件 src/runtime/map.go 中。我贴下相应的代码片段,如下:

// makehmap_small implements Go map creation for make(map[k]v) and
// make(map[k]v, hint) when hint is known to be at most bucketCnt
// at compile time and the map needs to be allocated on the heap.
func makemap_small() *hmap {
	h := new(hmap)
	h.hash0 = fastrand()
	return h
}

注意下,makemap_small 的返回值,它是一个指针,而不是具体的结构体,到这里也就明白了 fmt.Printf 打印的为什么是指针而不是具体某个值了。至于为什么不像 slice 那样直接返回结构体呢,我想或许是因为 hmap 的成员字段比较多吧。

到这里,问题似乎差不多都明白了,但我还发现一个问题。什么问题呢?为什么 slice 用 fmt.Printf(%p) 也是打印的指针,明明 make slice 返回的是结构体啊。slice 的 make 源码目录在 src/runtime/slice.go 中,相应代码如下:

func makeslice(et *_type, len, cap int) slice {
	// NOTE: The len > maxElements check here is not strictly necessary,
	// but it produces a 'len out of range' error instead of a 'cap out of range' error
	// when someone does make([]T, bignumber). 'cap out of range' is true too,
	// but since the cap is only being supplied implicitly, saying len is clearer.
	// See issue 4085.
	maxElements := maxSliceCap(et.size)
	if len < 0 || uintptr(len) > maxElements {
		panicmakeslicelen()
	}

	if cap < len || uintptr(cap) > maxElements {
		panicmakeslicecap()
	}

	p := mallocgc(et.size*uintptr(cap), et, true)
	return slice{p, len, cap}
}

的确返回的是 slice 结构体,而不是指针。那为什么 fmt.Printf(%p) 返回的是指针呢?这个问题要看下 fmt.Printf 的源码了,文件位置在 src/fmt/print.go,%p 的处理代码如下:

func (p *pp) fmtPointer(value reflect.Value, verb rune) {
	var u uintptr
	switch value.Kind() {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
		u = value.Pointer()
	default:
		p.badVerb(verb)
		return
	}

	switch verb {
	case 'v':
		...
	case 'p':
		p.fmt0x64(uint64(u), !p.fmt.sharp)
	case 'b', 'o', 'd', 'x', 'X':
		...
	default:
		...
	}
}

当为 p 格式时,执行指针格式化函数 fmtPointer,为什么 slice 的 Printf 打印的是指针,奥秘就是 value.Pointer 中,进去看下源码,如下:

func (v Value) Pointer() uintptr {
	// TODO: deprecate
	k := v.kind()
	switch k {
	case Chan, Map, Ptr, UnsafePointer:
		...
	case Func:
		...

	case Slice:
		return (*SliceHeader)(v.ptr).Data
	}
	panic(&ValueError{"reflect.Value.Pointer", v.kind()})
}

从代码可以看出,当打印的类型是 Slice 时,通过 Pointer 获取到的值是 Slice 底层数组的地址。

到此,你就应该全部明白了,为什么 map 和 slice 打印的都是地址,而不是它们的结构体。

推荐阅读:

如果 map 不是引用变量,那是什么?

资料下载

点击下方卡片关注公众号,发送特定关键字获取对应精品资料!

  • 回复「电子书」,获取入门、进阶 Go 语言必看书籍。

  • 回复「视频」,获取价值 5000 大洋的视频资料,内含实战项目(不外传)!

  • 回复「路线」,获取最新版 Go 知识图谱及学习、成长路线图。

  • 回复「面试题」,获取四哥精编的 Go 语言面试题,含解析。

  • 回复「后台」,获取后台开发必看 10 本书籍。

对了,看完文章,记得点击下方的卡片。关注我哦~ 👇👇👇

如果您的朋友也在学习 Go 语言,相信这篇文章对 TA 有帮助,欢迎转发分享给 TA,非常感谢!2eeb8de2cacd741ed93f9093e2697982.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值