ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

解析struct的内存布局

2022-01-08 23:34:33  阅读:160  来源: 互联网

标签:struct Alignof fmt unsafe 内存 Println 解析


解析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,Alignof,fmt,unsafe,内存,Println,解析
来源: https://www.cnblogs.com/caixunshi/p/15779987.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有