解析struct的内存布局
2022/1/9 7:03:31
本文主要是介绍解析struct的内存布局,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
解析struct的内存布局
在平时开发过程中,我们常用map[string]struct{}来实现一个Set,用struct{}的原因是struct{}不占用内存空间,为什么空struct会不占用内存空间?对于自定义的struct的内存空间的占用是什么样的?
struct的大小
struct和java中的对象类似,在内存中都是一块连续的空间,在java中,对象的引用其实就是一个对象在堆内存的内存地址起点,当访问第N个对象时,就是访问 (起点地址 + 第N个对象的地址偏移量)位置,在go中原理也类似,go struct的大小就是由struct中的属性决定的,例如:
type A struct { x int8 y int8 z int8 } var a A fmt.Println(unsafe.Sizeof(a)) // 3
可以看到输出结果:3,表示一个a对象在内存中占用3个字节,此时的内存结构如下:
struct的内存对齐
我们稍微修改一下字段的类型,然后再看下结果
type B struct { x int8 y int32 z int8 } var b B fmt.Println(unsafe.Sizeof(b)) // 12
这里输出结果会是8,按上面的理解int32占用4个字节,再加上两个1字节的int8,应该输出6才对,这个就引出go中的内存对齐机制了:
在计算机中,cpu在访问内存时,每次访问的并不是一个字节,而是一个字(word),一个字的字长由cpu的位数决定,例如32位的cpu一个字长位32bit,也就是4个字节,64位的cpu一个字长64bit,也就是8个字节,go为了减少在访问对象时cpu与内存的交互次数,会在编译时按照一定的规则进行内存对齐,防止访问一个对象的属性需要经历两次总线周期。
假如上面的B对象如果是6字节,则内存结构如下:
看上图,此时的cpu字长位1字节,那么访问y对象需要cpu需要和内存打两次交道
所以go中进行内存对齐之后内存结构如下:
黄色部分为内存对齐部分,这样在访问属性y时,cpu就只需要对内存进行一次读取了。
struct的对齐规则
内置unsafe包的Sizeof函数用来获取一个变量的大小,另外还有内置unsafe包的Alignof函数可以来获取一个变量的对齐系数,例如:
var a A fmt.Println(unsafe.Alignof(a.x)) // 1 fmt.Println(unsafe.Alignof(a.y)) // 4 fmt.Println(unsafe.Alignof(a.z)) // 1 fmt.Println(unsafe.Alignof(a)) // 4
具体规则如下:
- 对于任意类型的变量 x ,unsafe.Alignof(x) 至少为 1;
- 对于 struct 类型的变量 x,计算 x 每一个字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值;
- 对于 array 类型的变量 x,unsafe.Alignof(x) 等于构成数组的元素类型的对齐系数;
对齐系数必须是对象大小的整数倍,也就是说:在go中,对于任意对象a,等式unsafe.Sizeof(a) = X * unsafe.Aligof(a)必定成立
那有了这个等式,上面的对象B,X最小应该等于2,但是因为unsafe.Sizeof(b) = 12,所以X是等于3的,我们只需要调整一下字段顺序就可以将对象B占用的空间大小优化成8
type C struct { x int8 z int8 y int32 } var c C fmt.Println(unsafe.Sizeof(b)) // 8
此时的内存结构如下:
所以通过这个例子可以看出,合理的编排对象中属性的位置,可以减少对象的内存大小
嵌套struct
有了上面的基础,可以推断一下嵌套结构体和切片,数组的unsafe.Sizeof,unsafe.Aligniof大小,来练习一下
type D struct { x int a A b B arr [5]A sli []A } var d D fmt.Println(unsafe.Alignof(d)) // 8 fmt.Println(unsafe.Sizeof(d)) // 64 fmt.Println("---------") fmt.Println(unsafe.Alignof(d.x)) // 8 fmt.Println(unsafe.Sizeof(d.x)) // 8 fmt.Println("---------") fmt.Println(unsafe.Alignof(d.a)) // 1 fmt.Println(unsafe.Sizeof(d.a)) // 3 fmt.Println("---------") fmt.Println(unsafe.Alignof(d.b)) // 4 fmt.Println(unsafe.Sizeof(d.b)) // 12 fmt.Println("---------") fmt.Println(unsafe.Alignof(d.arr)) // 1 fmt.Println(unsafe.Sizeof(d.arr)) // 15 fmt.Println("---------") fmt.Println(unsafe.Alignof(d.sli)) // 8 fmt.Println(unsafe.Sizeof(d.sli)) // 24
其中a + b 做了内存对齐,填充了两个字节,arr也做了内存对齐,填充了一个字节
这里为什么切片是24个字节呢,这个和切片的结构体有关,在runtime/slice.go中有对slice的定义:
这里的len和cap在64位的cpu上都是占用8个字节的,array是一个指向底层数组的指针,也是占用8个字节,所以加起来一共就是24个字节。
这篇关于解析struct的内存布局的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享