在 Go 语言中,len
函数可以用于获取一个变量的长度,unsafe.Sizeof
函数用于获取一个数组变量的总大小。
len
函数比较简单好理解,使用过程中基本上不会遇到问题,这篇笔记主要介绍 unsafe.Sizeof
。
一个问题
先来看看这段示例代码:
1 | package main |
执行之后,得到的输出如下:
1 | 数组的长度: 4 |
会不会有些意外?
切片、string、map 的总大小怎么有点奇怪?怎么是 24、16 和 8?
sizeof
回答这个问题之前,先来看看 unsafe.sizeof
这个函数的定义:
1 | // $GOROOT/src/unsafe/unsafe.go |
大意就是:Sizeof 接受任何类型的表达式 x,并返回一个假设变量 v 的字节大小,就好像 v 是通过 var v=x 声明的,该大小不包括 x 可能引用的任何内存。
例如,如果 x 是一个切片,Sizeof 返回切片描述符的大小,而不是切片引用的内存大小。
什么是描述符呢?
以切片为例,就是它本身并不真正存储字符串数据,而仅是由一个指向底层存储的指针、切片长度和切片最大容量组成。
看到这里会不会清晰一些,有没有想起什么,没错,切片的数据结构刚好是由这三部分组成:
- array:指向底层数组的指针,类型为 uintptr,占用八个字节
- len:切片的长度,类型为 int,占用八个字节
- cap:切片的最大容量,类型为 int,占用八个字节
所以,在上面的示例代码中,切片变量的总大小就是 8 + 8 + 8 = 24
个字节。
string 类型也是一样的分析方式,它的描述符是由一个指向底层存储的指针和字符串的长度组成。
string 的数据结构:
- Data:指向底层存储的指针,类型为 uintptr,占用八个字节
- Len:字符串的长度,类型为 int,占用八个字节
string 变量的总大小就是 8 + 8 = 16
个字节。
结构体作为直接存储自身数据的类型,它和数组又有所不同,这是因为结构体是由若干个字段(field)聚合而成,每个字段都有自己的类型,所以结构体占用内存大小取决于组成结构体的各字段大小之和。
还是上面的示例代码,Person 结构体是由一个 string 类型、两个 int 类型组成,所以它占用的内存大小是 16 + 8 + 8 = 32
个字节。
注意,这里的string 就是上面的 string ,所以是 16 个字节。
因为空结构体中没有字段,所以总大小是零。
总结
对于整型、数组、结构体这类类型,它们的内存表示就是它们自身的数据内容,所以计算占用内存大小时,就是组成它们数据本身的大小。
而对于切片、string、map 等类型来说,它们的内存表示则是它们数据内容的“描述符”,所以计算占用内存大小时,需要以描述符的大小为准。
前者作为函数参数传递时,常被提到有性能开销,而后者则没有,也正是这个原因。