文章内容来自 《Go101》

Go101.org


我们称一个Go值分布在不同内存块上的部分为此值的各个值部(value part)。 一个分布在多个内存块上的值含有一个直接值部和若干被此直接值部引用着的间接值部。

上面的段落描述了两个类别的Go类型。下表将列出这两个类别(category)中的类型(type)种类(kind):

接口类型和字符串类型值是否包含间接部分取决于具体编译器实现。 如果不使用今后将介绍的非类型安全途径,我们无法从这两类类型的值的外在表现来判定它们的值是否含有间接部分。 在《Go语言101》中,我们认为这两类类型的值是可能包含间接值部的。
同样地,函数类型的值是否包含间接部分几乎也是不可能验证的。 在《Go语言101》中,我们认为函数值是可能包含间接值部的。

Go中的两种指针类型

Go还支持另一种称为非类型安全的指针类型。 非类型安全的指针类型提供在unsafe标准库包中。

非类型安全指针类型通常使用unsafe.Pointer来表示。
unsafe.Pointer类似于C语言中的void*。

一个指针值存储着另一个值的地址,除非此指针值是一个nil空指针。 我们可以说此指针引用着(第15章)另外一个值,或者说另外一个值正被此指针所引用。 一个值可能被间接引用,比如
如果一个结构体值a含有一个指针字段b并且这个指针字段b引用着另外一个值c,那么我们可以说结构体值a也引用着值c。
如果一个值x(直接或者间接地)引用着另一个值y,并且值y(直接或者间接地)引用着第三个值z,则我们可以说值x间接地引用着值z。

以后,我们将一个含有(直接或者间接)指针字段的结构体类型称为一个指针包裹类型,将一个含有(直接或者间接)指针的类型称为指针持有者类型。 指针类型和指针包裹类型都属于指针持有者类型。元素类型为指针持有者类型的数组类型也是指针持有者类型

映射、通道和函数类型的内部定义

在赋值中,底层间接值部将不会被复制

在Go中,每个赋值操作(包括函数调用传参等)都是一个值的浅复制过程(假设源值和目标值的类型相同)。 换句话说,在一个赋值操作中,只有源值的直接部分被复制给了目标值。 如果源值含有间接部分,则在此赋值操作完成之后,目标值和源值的直接部分将引用着相同的间接部分。 换句话说,两个值将共享底层的间接值部,

事实上,对于字符串值和接口值的赋值,上述描述在理论上并非百分百正确。 官方FAQ 🗗 明确说明了在一个接口值的赋值中,接口的底层动态值将被复制到目标值。 但是,因为一个接口值的动态值是只读的,所以在接口值的赋值中,官方标准编译器并没有复制底层的动态值。这可以被视为是一个编译器优化。 对于字符串值的赋值,道理是一样的。所以对于官方标准编译器来说,上一段的描述是100%正确的。
因为一个间接值部可能并不专属于任何一个值,所以在使用unsafe.Sizeof函数计算一个值的尺寸的时候,此值的间接部分所占内存空间未被计算在内。

我们来验证一下,string是不是底层是由一个指向字符串的指针还有len组成的

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	s1 := "helloss"
	s2 := s1

	fmt.Printf("%p\n", &s1)                                  // s1 的变量地址
	fmt.Printf("%p\n", &s2)                                  // s2 的变量地址
	length := (int)(((*[2]uintptr)(unsafe.Pointer(&s1)))[1]) //取底层length
	dataPtr := (*[2]uintptr)(unsafe.Pointer(&s1))[0]         //取底层data
	data := unsafe.Slice((*byte)(unsafe.Pointer(dataPtr)), length)
	fmt.Printf("%s\n", string(data))
	fmt.Printf("%p\n", unsafe.Pointer((*[2]uintptr)(unsafe.Pointer(&s1))[0])) // s1 的底层数据地址
	fmt.Printf("%p\n", unsafe.Pointer((*[2]uintptr)(unsafe.Pointer(&s2))[0])) // s2 的底层数据地址
}

“引用”这个术语在Go社区中使用得有些混乱。很多Go程序员在Go编程中可能由此产生了一些困
惑。 一些文档或者网络文章,包括一些官方文档 🗗 ,把“引用”(reference)看作
是“值”(value)的一个对立面。 《Go语言101》强烈不推荐这种定义。在这一点上,本人不想争论什么。这里仅仅列出一些肯定错误地使用了“引用”这个术语的例子:
在Go中,只有切片、映射、通道和函数类型属于引用类型。 (如果我们确实需要引用类型
这个术语,那么我们不应把其它指针持有者类型排除在引用类型之外。)
一些函数调用的参数是通过引用来传递的。 (对不起,在Go中,所有的函数调用的参数都
是通过值复制直接值部的方式来传递的。)
我并不是想说引用类型这个术语在Go中是完全没有价值的, 我只是想表达这个术语是完全没有必要的,并且它常常在Go的使用中导致一些困惑。我推荐使用指针持有者类型来代替这个术语。 另外,我个人的观点是最好将引用这个词限定到只表示值之间的关系,把它当作一个动词或者名词来使用,永远不要把它当作一个形容词来使用。 这样将在使用Go的过程中避免很多困惑。