Go源码学习: string的内部数据结构
2021-11-15
前面在学习Go程序进程的内存布局时,分析了一个Go程序在Linux下ELF文件格式经过简化后大致如下图所示:
Go二进制文件ELF主要有这几个section组成: .text
, .rodata
, .data
, .noptrdata
, .bss
, .noptrbss
。
.rodata
中存放的是常量数据,程序中的字面量在编译时会被放到这个section中,那么字符串字面量也将放到这个section中。
在执行程序时,ELF文件被加载到内存中,相同权限的Section会被对应到一个Segment中,大致是下图的内存加载结构:
程序在运行时字符串字面量在内存结构中的代码区。本文将学习Go语言中string的内部数据结构。
string的内部数据结构 #
string在Go中的内部结构是reflect.StringHeader
位于reflect/value.go
。
1// StringHeader is the runtime representation of a string.
2// It cannot be used safely or portably and its representation may
3// change in a later release.
4// Moreover, the Data field is not sufficient to guarantee the data
5// it references will not be garbage collected, so programs must keep
6// a separate, correctly typed pointer to the underlying data.
7type StringHeader struct {
8 Data uintptr
9 Len int
10}
从StringHeader的注释中可以看出: StringHeader是string在运行时的表示形式,它本身不存储string的数据,而只包含一个指向string底层存储的指针(Data uinptr
)和一个表示string长度的int字段(Len int
),string的底层存储是一个byte类型的数组。
关于
uintptr
Go语言规范https://golang.org/ref/spec中是这样描述的an unsigned integer large enough to store the uninterpreted bits of a pointer value
,是一个无符号整形大到可以存储任意一个指针的值。对比uint
是和平台相关的无符号整形,在32位机器上是32位无符号整形,在64位机器上是64位无符号整形。
在Go中unsafe.Pointer
表示任意类型的指针,可以将一个string的地址显式转换成unsafe.Pointer
,就可以进一步显式转换成*reflect.StringHeader
了:
1package main
2import (
3 "fmt"
4 "reflect"
5 "unsafe"
6)
7func main() {
8 s := "hello"
9 sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
10 fmt.Printf("%+v\n", sh) // &{Data:17446332 Len:5}
11 fmt.Println(sh.Len) // 5
12 ptr := unsafe.Pointer(sh.Data)
13 arrPtr := (*[5]byte)(ptr)
14 fmt.Println(*arrPtr) // [104 101 108 108 111]
15}
上面的代码在第9行得到sh这个reflect.StringHeader
类型的指针,后边可以获取它的底层数据数组指针,并继续通过通用指针unsafe.Pointer
的转换能力得到底层字节数组。
有了上面对string内部数据结构了解,就不难立即当使用refect.Sizeof
函数获取string类型占用的字节数时,在64位机器上,无论字符长度是多少,返回值都是16,即16个字节,这是因为refect.Sizeof
获取string类型占用字节数时,关注的是reflect.StringHeader
,而StringHeader中Data和Len两个字段一个是uintptr
,一个是int
,在64位机器上各占8个字节,所以返回的是16。
1package main
2import (
3 "fmt"
4 "unsafe"
5)
6func main() {
7 s := "hello world"
8 fmt.Println(unsafe.Sizeof(s)) // 16
9}