博客 记录生活,记录工作。

golang基础-05-数组&切片&string变量的存储结构

2019-03-03
gongmh
go

上文分析了,单个基础变量的内存存储结构,下面我们分析下基础变量组合形式的变量。

1. 数组(array)

int32的数组进行分析,运行一下

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var arrVar = [...]int32{
		'b', 2, 3, 4,
	}

	fmt.Printf("arrVar 值: %d\n", arrVar)
	fmt.Printf("arrVar 内存地址: %p\n", &arrVar)
	fmt.Printf("arrVar 内存中占总字节数:%d\n", unsafe.Sizeof(arrVar))
	fmt.Printf("arrVar 元素个数:%d\n", len(arrVar))

	//第一个元素
	ptr1 := (*int32)(unsafe.Pointer(&arrVar))
	fmt.Printf("arrVar 第一个元素,地址=%x,值: %d\n", ptr1, *ptr1)

	//第二个元素
	ptr2 := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&arrVar)) + uintptr(4*1)))
	fmt.Printf("arrVar 第二个元素,地址=%x,值: %d\n", ptr2, *ptr2)

	//第三个元素
	ptr3 := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&arrVar)) + uintptr(4*2)))
	fmt.Printf("arrVar 第三个元素,地址=%x,值: %d\n", ptr3, *ptr3)

	//第四个元素
	ptr4 := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(&arrVar)) + uintptr(4*3)))
	fmt.Printf("arrVar 第四个元素,地址=%x,值: %d\n", ptr4, *ptr4)
}

输出结果如下:

arrVar 值: [98 2 3 4]
arrVar 内存地址: 0xc000016050
arrVar 内存中占总字节数:16
arrVar 元素个数:4
arrVar 第一个元素,地址=c000016050,值: 98
arrVar 第二个元素,地址=c000016054,值: 2
arrVar 第三个元素,地址=c000016058,值: 3
arrVar 第四个元素,地址=c00001605c,值: 4

结果可以看出,数组的元素类型是int32,每个占用4个字节,一共4个元素,因此数组总共占用16字节。数组的首地址是c000016050对应数组第一个元素,最后一个元素的首地址是c00001605c,可以得出数组元素连续分配,并且地址从低到高排布。

2. 切片(slice)

仍然使用int32类型,对切片进行分析,运行一下


package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var arrVar = []int32{
		'b', 2, 3, 4,
	}

	fmt.Printf("arrVar 值: %d\n", arrVar)
	fmt.Printf("arrVar 内存地址: %p\n", &arrVar)
	fmt.Printf("arrVar 内存中占总字节数:%d\n", unsafe.Sizeof(arrVar))
	fmt.Printf("arrVar 元素个数:%d\n", len(arrVar))
}

输出结果如下:

arrVar 值: [98 2 3 4]
arrVar 内存地址: 0xc00009c020
arrVar 内存中占总字节数:24
arrVar 元素个数:4

可以看出,切片4个元素,但是切片占用的总字节数为24字节。可以自行试下,无论切片多少元素,slice变量本身占用的字节数不变,都是24字节。

分析golang源码(go1.11)在文件src/runtime/slice.go中可以看到如下结构:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

说明slice实际上是由一个指针和两个int变量组成,指针和int都是占8个字节,也就对应了上面的slice总共占用24字节。继续用slice结构体对切片进行分析,运行一下

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var sliceVar = []int32{
		'b', 2, 3, 4,
	}

	fmt.Printf("sliceVar 值: %d\n", sliceVar)

	type sliceStruct struct {
		array unsafe.Pointer
		len   int
		cap   int
	}

	//将切片转换为sliceStruct结构
	ptr := (*sliceStruct)(unsafe.Pointer(&sliceVar))
	fmt.Printf("sliceVar 地址=%p,值: %#v\n", &sliceVar, *ptr)

	//取出sliceStruct中指针,再找指针值对应地址内存的值
	arrPtr := (*[4]int32)(ptr.array)
	fmt.Printf("sliceVar 中指针指向的值: %#v\n", *arrPtr)
}

执行结果如下:

sliceVar 值: [98 2 3 4]
sliceVar 地址=0xc00000c060,值: main.sliceStruct{array:(unsafe.Pointer)(0xc000016050), len:4, cap:4}
sliceVar 中指针指向的值: [4]int32{98, 2, 3, 4}

切片对应的结构体中,元素array指向的是切片元素存储的数组地址,元素lencap分别对应切片的长度和容量。也对应之前知道的知识,切片底层是数组,一个数组可以被多个切片引用,感兴趣的可以自己试验下。

3. 字符串(string)

字符串的结构类似切片,不过字符串是不可被改变的,也就只有指针和长度。运行一下

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var stringVar string = "hi"

	fmt.Printf("sliceVar 值: %s\n", stringVar)
	fmt.Printf("sliceVar 内存中占总字节数:%d\n", unsafe.Sizeof(stringVar))

	type stringStruct struct {
		array unsafe.Pointer
		len   int
	}

	//将切片转换为sliceStruct结构
	ptr := (*stringStruct)(unsafe.Pointer(&stringVar))
	fmt.Printf("sliceVar 地址=%p,值: %#v\n", &stringVar, *ptr)

	//取出sliceStruct中指针,再找指针值对应地址内存的值
	arrPtr := (*[5]byte)(ptr.array)
	fmt.Printf("sliceVar 中指针指向的值: %c, %c\n", (*arrPtr)[0], (*arrPtr)[1])
}

执行结果如下:

sliceVar 值: hi
sliceVar 内存中占总字节数:16
sliceVar 地址=0xc000010210,值: main.stringStruct{array:(unsafe.Pointer)(0x4c1b71), len:2}
sliceVar 中指针指向的值: h, i

字符串包含指针和int两个元素,固定占用16个字节。其中指针指向一个byte的数组,int元素代表的是字符串的长度。

4. 总结

从以上可以看出,数组是一段连续的内存空间。而切片和字符串本身是一个指针,指针再指向具体的内容的数组。


相关博文

评论