在开始之前,我希望你能计算一下part1占用的总大小。
输出结果:
经计算,Part1结构体占用内存大小为1+4+1+8+1=15字节。我想有些朋友是这么算的,似乎并没有什么问题。
实际情况是怎样的呢?我们看一下实际调用如下:
输出结果:
最终输出占用32字节。这与之前预想的结果完全不同。这充分证明了之前的计算方法是错误的。为什么?
这里需要提到“内存对齐”的概念,以便我们能够以正确的姿势进行计算。接下来详细说一下它是什么。
有的朋友认为读取内存只是字节数组的简单排列。
上图展示了一个陷阱和胡萝卜读取记忆的方法。然而CPU实际上并不是逐字节读写内存的。相反,CPU一次读取内存一个块,块大小可以是2、4、6、8、16字节等。我们将块大小称为内存访问的粒度。如下所示:
示例假设访问粒度为4。CPU以每4字节的访问粒度读写内存。这才是正确的态度。
另外,作为一名工程师,这个知识点你是有必要学习的:)
上图中,假设阅读开始来自索引1。将会出现崩溃问题。因为它的内存访问边界没有对齐。所以CPU会做额外的处理工作。如下:
从上面的过程可以得出,不做“内存对齐”有点“繁琐”。由于这样增加了很多耗时的动作
假设完成了内存对齐,从index0读取了4个字节,那么只需要读取一次,不需要额外的操作。这显然更加高效,是一种以空间换时间的标准方式
不同平台上的编译器都有自己默认的“对齐系数”,可以通过#pragmapack(n)预编译命令进行更改。n指“对准系数”。一般来说,我们常用的平台的系数如下:
另外,需要注意的是,不同的硬件平台所占用的尺寸和方向值可能会有所不同。因此,本文中的数值并不明确。调试时需要考虑机器的实际情况。
输出结果:
在Go中,可以不安全地调用.Alignof返回对应类型的对齐情况。通过观察输出结果可以看到,基本都是2^n,最大不会超过8。这是因为我的笔记本电脑编译器(64位)的默认对齐因子是8,所以最大值不会超过这个数字。
上一节提到了结构体中的成员变量必须是面向字节的。因此,当然,作为最终结果的结构也必须是面向字节的。
接下来,我们分析“它”经历了什么,影响了“预期”的结果
在每个之后member变量是对齐的。根据规则2,整个结构体本身也一定是面向字节的,因为可以发现它可能不是2^n,也不是偶数倍。显然不符合对齐规则
根据规则2,可以得出对齐值为8。偏移量现在为25,它不是8的倍数。所以偏移量为32。对齐结构体
Part1内存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
通过本节的分析我们可以看到前面的“为什么”是“计算”错了吗?
这是因为实际的内存管理并不是基于“一根胡萝卜一个坑”的想法。但一点一点地。这篇读写文章是通过用空间换取时间(效率)的思想来完成的。另外,还需要考虑到不同平台的内存运行情况。
在上一节中可以看出,结构体的内存根据成员变量的类型而变化,对齐和其他操作。那么如果我们假设场序不同的话,会不会有什么变化呢?让我们一起尝试一下:-)
输出结果:
根据结果,我们会惊讶地发现,即使是成员变量字段顺序的“简单”更改也会改变Body结构占用尺寸
接下来我们一起来分析part2,看看它的内部和之前有什么不同导致了这样的结果?
符合规则2,无需额外对齐
Part2内存布局:ecax|bbbb|dddd|dddd
通过比较part1和part的内存布局2:你会发现两者有很大的区别。如下:
仔细一看,Part1中有很多padding。显然它占用了大量空间。内饰是什么样子的?
通过本文的介绍,我们可以看到,不同类型需要字节对齐来保证内存访问边界
就不难理解为什么结构体要调整字段顺序了成员变量的数量可以减少结构的大小,因为它巧妙地减少了填充的存在。让它们更加“紧凑”。这对于加深对Go内存布局的印象以及优化大对象非常有帮助
上一篇:ccd内存卡2g能拍多少照片
下一篇:win10内存管理机制