文章目录

GoLang之Map变量输出地址问题

1.Map变量输出地址问题

初始化了一个map型的变量m,使用printf(%p)的格式分别对m和&m进行输出,分别得到两个地址。
&m显而易见是m变量的地址,令我困惑的是m也可以输出一个地址,为什么m却可以输出地址?


func main() {
	var a int
	fmt.Printf("a : %p\n", a)   //a : %!p(int=0)
	fmt.Printf("&a: %p\n ", &a) //&a: 0xc0000aa058
	m := make(map[int]int)
	fmt.Printf("m : %p\n", m)   // m : 0xc0000c2450
	fmt.Printf("&m: %p\n ", &m) //&m: 0xc0000ce020

}

func main() {
	var a int
	fmt.Printf("a : %p\n", a)   //a : %!p(int=0)
	fmt.Printf("&a: %p\n ", &a) //&a: 0xc000016098
	var m map[int]int
	fmt.Printf("m : %p\n", m)   // m : 0x0
	fmt.Printf("&m: %p\n ", &m) //&m: 0xc000006030
}

可以看下 map 的初始化函数 make 的源码。make 可用于 map、slice、chan 三种类型,每个类型都有相应的 make 实现,比如,以下为map 的 make 实现源码文件
makemap_small 的返回值,它是一个指针,而不是具体的结构体,到这里也就明白了 fmt.Printf 打印的为什么是指针而不是具体某个值了。至于为什么不像 slice 那样直接返回结构体呢,我想或许是因为 hmap 的成员字段比较多吧。

//makemap_small()函数在runtime/map.go里
// makemap_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
}

但我还发现一个问题。什么问题呢?为什么 slice 用 fmt.Printf(%p) 也是打印的指针,明明 make slice 返回的是结构体
以下为slice的 make实现源码文件:
的确返回的是 slice 结构体,而不是指针。那为什么 fmt.Printf(%p) 返回的是指针呢?这个问题要看下 fmt.Printf 的源码了

func main() {
	var a [][]int
	fmt.Printf("a : %p\n", a)   //a : 0x0
	fmt.Printf("&a: %p\n ", &a) //&a: 0xc000004078
	m := make([][]int, 0)
	fmt.Printf("m : %p\n", m)   //  m : 0xe61438
	fmt.Printf("&m: %p\n ", &m) //&m: 0xc000004090
}
//以下函数在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)
	iflen < 0 || uintptr(len) > maxElements {
		panicmakeslicelen()
	}

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

	p := mallocgc(et.size*uintptr(cap), et, true)
	return slice{p, len, cap}
}
//slice结构体在runtime/slice.go里
type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

fmt.Printf 的%p 的的源码处理代码如下:
当为 p 格式时,执行指针格式化函数 fmtPointer,为什么 slice 的 Printf 打印的是指针,奥秘就是 value.Pointer 中,进去看下源码,如下:
从代码可以看出,当打印的类型是 Slice 时,通过 Pointer 获取到的值是 Slice 底层数组的地址。
到此,你就应该全部明白了,为什么 map 和 slice 打印的都是地址,而不是它们的结构体。

//fmt.Printf 的%p 在 src/fmt/print.go里
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:
		...
	}
}
//以下代码在src/reflect/value.go里
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()})
}

2.附

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	s := []int{1, 2, 3, 4}
	fmt.Printf("%p\n", s)  //0xc00000e1c0
	fmt.Printf("%p\n", &s) //0xc000004078

	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(hdr)        //&{824633778624 4 4}
	fmt.Printf("%p\n", hdr) //0xc000004078

	a := hdr.Data
	fmt.Println(a) //824633778624
	b := (*[4]int)(unsafe.Pointer(hdr.Data))
	fmt.Println(b)        //&[1 2 3 4]
	fmt.Printf("%p\n", b) //0xc00000e1c0

	data := *(*[4]int)(unsafe.Pointer(hdr.Data))
	fmt.Println(data) //[1 2 3 4]

	//浅拷贝
	s1 := s
	fmt.Printf("%p\n", s1)  //0xc00000e1c0
	fmt.Printf("%p\n", &s1) //0xc0000040a8
}