内存对齐的规则以及作用[通俗易懂]

内存对齐的规则以及作用

大家好,又见面了,我是全栈君。

内存对齐能够用一句话来概括:

“数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上”
比如int类型占用4个字节。地址仅仅能在0,4。8等位置上。

由一个程序引入话题:

//环境:vc6 + windows sp2
//程序1
#include <iostream>

using namespace std;

struct st1 
{
    char a ;
    int  b ;
    short c ;
};

struct st2
{
    short c ;
    char  a ;
    int   b ;
};

int main()
{
    cout<<"sizeof(st1) is "<<sizeof(st1)<<endl;
    cout<<"sizeof(st2) is "<<sizeof(st2)<<endl;
    return 0 ;
}


程序的输出结果为:

 sizeof(st1) is 12

        sizeof(st2) is 8 

 

问题出来了。这两个一样的结构体,为什么sizeof的时候大小不一样呢?

本文的主要目的就是解释明确这一问题。

 

内存对齐,正是由于内存对齐的影响,导致结果不同。

对于大多数的程序猿来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每一个数据单元安排在合适的位置上,从而导致了同样的变量,不同声明顺序的结构体大小的不同。

 

       那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7。4(int) + 2(short) + 1(char) = 7 。

经过内存对齐后,结构体的空间反而增大了。

在解释内存对齐的作用前,先来看下内存对齐的规则:

1、  对于结构的各个成员。第一个成员位于偏移为0的位置,以后每一个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。

2、  在数据成员完毕各自对齐之后,结构(或联合)本身也要进行对齐,对齐将依照#pragma pack指定的数值和结构(或联合)最大数据成员长度中。比較小的那个进行。

 

#pragma pack(n) 表示设置为n字节对齐。

 VC6默认8字节对齐

以程序1为例解释对齐的规则 :

St1 :char占一个字节。起始偏移为0 ,int 占4个字节,min(#pragma pack()指定的数。这个数据成员的自身长度) = 4(VC6默认8字节对齐),所以int按4字节对齐。起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会加入3个字节的额外字节,不存放随意数据。short占2个字节,按2字节对齐。起始偏移为8,正好是2的倍数,无须加入额外字节。到此规则1的数据成员对齐结束。此时的内存状态为:

oxxx|oooo|oo

0123 4567 89 (地址)

(x表示额外加入的字节)

共占10个字节。还要继续进行结构本身的对齐。对齐将依照#pragma pack指定的数值和结构(或联合)最大数据成员长度中。比較小的那个进行,st1结构中最大数据成员长度为int。占4字节,而默认的#pragma pack 指定的值为8,所以结果本身依照4字节对齐。结构总大小必须为4的倍数,需加入2个额外字节使结构的总大小为12 。

此时的内存状态为:

oxxx|oooo|ooxx

0123 4567 89ab  (地址)

到此内存对齐结束。St1占用了12个字节而非7个字节。

 

St2 的对齐方法和st1同样,读者可自己完毕。

 

内存对齐的主要作用是:

1、  平台原因(移植原因):不是全部的硬件平台都能訪问随意地址上的随意数据的;某些硬件平台仅仅能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、  性能原因:经过内存对齐后,CPU的内存訪问速度大大提升。

详细原因稍后解释。

 

图一:


内存对齐的规则以及作用[通俗易懂]

这是普通程序猿心目中的内存印象。由一个个的字节组成。而CPU并非这么看待的。

 

图二:

内存对齐的规则以及作用[通俗易懂]

CPU把内存当成是一块一块的,块的大小能够是2,4。8。16字节大小。因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。

 

如果CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:

1、数据从0字节開始

2、数据从1字节開始

 

再次如果内存读取粒度为4。

 

图三:

内存对齐的规则以及作用[通俗易懂]

当该数据是从0字节開始时,非常CPU仅仅需读取内存一次就可以把这4字节的数据全然读取到寄存器中。

    当该数据是从1字节開始时,问题变的有些复杂。此时该int型数据不是位于内存读取边界上。这就是一类内存未对齐的数据。

 

图四:

内存对齐的规则以及作用[通俗易懂]

 

此时CPU先訪问一次内存。读取0—3字节的数据进寄存器。并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除。最后合并1,2,3。4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作。大大减少了CPU性能。

    这还属于乐观情况了。上文提到内存对齐的作用之中的一个为平台的移植原因,由于以上操作仅仅有有部分CPU肯干,其它一部分CPU遇到未对齐边界就直接罢工了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/116525.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 2021年1月25日博客日记「建议收藏」

    2021年1月25日博客日记「建议收藏」今天尝试了一下ART-PI的串口终端,发现使用串口就可以向板子发送shell命令,这可能就是他的操作系统吧!还不是很清楚,接着摸索这个操作系统究竟是怎么回事?成功驱动OLED显示屏,网上找到的例子好像不对,按着他的就不行,瞎鼓捣了一下发现可以,oled显示屏太low了,可能配不上这个板子,打算买个RGB的显示屏,一开淘宝上,快两百块钱了,群友打算出二手显示屏,算上OV6070摄像头、运费、还有转接板一共在150左右,虽然也不怎么便宜,但是人家已经在ART上面做过测试了,能正常运行,他不给代码,还是得自己去研

    2022年5月31日
    27
  • mac idea 激活码(JetBrains全家桶)「建议收藏」

    (mac idea 激活码)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月22日
    112
  • 51单片机控制TB6600驱动器驱动42步进电机「建议收藏」

    51单片机控制TB6600驱动器驱动42步进电机「建议收藏」51单片机控制TB6600驱动器驱动42步进电机功能实现目标硬件简介硬件连接程序代码功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程图导出与…

    2022年5月20日
    56
  • iPad如何越狱?4.2.1完美越狱教程 一 (DFU 绿霸越狱)[通俗易懂]

    iPad如何越狱?4.2.1完美越狱教程 一 (DFU 绿霸越狱)[通俗易懂] 4.2.1完全越狱图文教程.如何用Cydia安装程序  Cydia“源安装”方式安装程序  Cydia“搜索安装”方式安装程序  Cydia“分类安装”方式安装程序  如何卸载用Cydia安装的程序  如何更新用Cydia安装的程序  关于SHSH的说明  Cydia总结.如何用Installous4直接安装程序?…

    2022年9月19日
    0
  • navicat premium15激活码【2021.10最新】

    (navicat premium15激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html08G05E7DZH-eyJsa…

    2022年3月28日
    53
  • redis如何设置密码及验证密码_redis设置永不过期

    redis如何设置密码及验证密码_redis设置永不过期密码设置这里简单介绍一下redis如何设置密码redis密码设置有两种方式,一种需要重启redis服务,一种不需要重启redis服务。首先,介绍一下需要重启redis服务的设置方式即找到redis的配置文件—redis.conf文件,然后修改里面的requirepass,这个本来是注释起来了的,将注释去掉,并将后面对应的字段设置成自己想要的密码,保存退出。重启redis服务,即可。我…

    2022年4月19日
    76

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号