一文带你搞懂Golang结构体内存布局

2022-10-22,,,,

前言

结构体在go语言中是一个很重要的部分,在项目中会经常用到,大家在写go时有没有注意过,一个struct所占的空间不一定等于各个字段加起来的空间之和,甚至有时候把字段的顺序调整一下,struct的所占空间不一样,接下来通过这篇文章来看一下结构体在内存中是怎么分布的?通过对内存布局的了解,可以帮助我们写出更优质的代码。感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

结构体内存布局

结构体大小

结构体实际上就是由各种类型的数据组合而成的一种符合数据类型,一个结构体变量的大小是由结构体中的字段决定。结构体和它所包含的数据在内存中是以连续块的形式存在的。我们可以借助unsafe.sizeof方法,来获取:

package main

import (
	"fmt"
	"unsafe"
)

type test struct {
	t1 int8 // 1
	t2 int8 // 1
	t3 int8 // 1
}
func main() {
    var t test
	fmt.println(unsafe.sizeof(t)) //3 bytes

}

内存对齐

不同类型的变量占用内存大小是不一样的,但是cpu每次读取的内存长度是固定的(比如cpu是64位的,一次可以从内存中读取64位的数据,即8个字节),为了cpu能高效的读写数据,编译器会把各种类型的数据放在合适的地址,而不是顺序的一个接一个的排放,并占用合适的长度,这就是内存对齐。每种类型的对齐值就是它的对齐边界。

示例:

package main

import (
	"fmt"
	"unsafe"
)
type test struct {
	a int8 // 1
	b int64 // 8
	c int32 // 4
}
type test2 struct {
	a int8 // 1
	b int32 // 4
	c int64 // 8
}
func main() {
    var t test
	fmt.println(unsafe.sizeof(t)) // 24
	var t2 test2
	fmt.println(unsafe.sizeof(t2))// 16
}

通过上面示例,我们可以看到两个结构体中3个字段类型相同,当排列顺序发生变化时,总的内存大小也会发生变化。下面我们来一起分析一下:

如果没有内存对齐,那结构体各个字段在内存中是紧密排列的,如t1内存布局示意图如下:

因为b这个字段需要8个字节,所以会有一个字节的数据排列到第2个字中。如果程序想要读取b字段的数据,那么cpu需要两次读取才能获取到完整的数据,这样就会影响了程序的性能。

所以,为了能让cpu减少一次获取的时间,go编译器会帮你把struct结构体做数据的对齐,以便cpu可以一次将该数据从内存中读取出来。重新排列后内存布局结构示意图如下:

其中有13个字节是真正存储数据的,而灰色的11个字节则是为了对齐而填充上的,不存储任何数据,所以才会比没有对齐排列时多出11个字节。

虽然通过对齐填充的方式提高了cpu读写数据的效率,但是这些填充内存确实有点浪费空间,那有没有办法既可以既可以做到内存对齐保证cpu读写效率又能减少浪费内存空间呢?

那就是调整struct字段的顺序,我们在来看一下t2结构体的字段内存布局结构示意图如下:

这样重新排列后,只占了16个字节,比上面那种方式少了8个字节。由此可知,对结构体字段的重新排列会让结构体更节省内。

总结

本篇文章我们一起学习了go 语言中的内存对齐,主要内容如下:

  • 结构体是占用一块连续的内存,一个结构体变量的大小是由结构体中的字段决定。
  • unsafe.sizeof(x) 返回了变量x的内存占用大小。
  • 两个结构体,即使包含变量类型的数量相同,但是位置不同,占用的内存大小也不同,由此引出了内存对齐。
  • 对结构体字段的重新排列会让结构体更节省内。

到此这篇关于一文带你搞懂golang结构体内存布局的文章就介绍到这了,更多相关golang结构体内存布局内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

《一文带你搞懂Golang结构体内存布局.doc》

下载本文的Word格式文档,以方便收藏与打印。