一文读懂内存对齐的规则和作用

摘要:
内存对齐,正是因为内存对齐的影响,导致结果不同。经过内存对齐后,结构体的空间反而增大了。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。内存对齐也可以认为是空间换时间。

引入:两个相同变量,不同顺序的结构体占用的内存大小不一样

  • 开发环境Windows10 64位 Visual Studio 2019
    一文读懂内存对齐的规则和作用第1张

探究:

  • 开发环境Windows10 64位 Visual Studio 2019
#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; //sizeof(st1) is 12
   cout<<"sizeof(st2) is "<<sizeof(st2)<<endl; //sizeof(st2) is 8
   return 0 ;
}
  • 问题出来了,这两个一样的结构体,为什么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就是对齐系数。#pragma pack(n) 表示设置为n字节对齐。visual studio 2019默认8字节对齐
  • 根据内存对齐规则分析结构体St1:
    • 1、St1 :char占一个字节,起始偏移为0 ,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度) = 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为:
      • 第一步
      • occc|oooo|oo
      • 0123 4567 89 (地址)
      • (c表示额外添加的字节)
    • 2、共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,需添加2个额外字节使结构的总大小为12 。此时的内存状态为:
      • 第二步
      • occc|oooo|oocc
      • 0123 4567 89ab (地址)
    • 到此内存对齐结束。St1占用了12个字节而非7个字节。
  • St2 的对齐方法和st1相同:
    • oo|o|oooo
    • 0123 4567 (地址)
    • (c表示额外添加的字节)
    • oo|oc|oooo
    • 0123 4567 8 (地址)
    • (c表示额外添加的字节)
    • 对于St2第二步额外添加的字节的位置要保证每一块的大小是2的n次幂
  • 内存对齐的主要作用是
    • 1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
    • 2、性能原因:经过内存对齐后,CPU的内存访问速度大大提升。下文讲述具体原因:
  • 内存对齐对性能提升的原因:
    • 图一:普通程序员心目中的内存印象,由一个个的字节组成。
      普通程序员心中的内存
      而CPU并不是这么看待的。
    • 图二:
      图二
      CPU把内存当成是一块一块的,块的大小可以是2,4,8,16,甚至32字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小称为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。
      假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
      1、数据从0字节开始
      2、数据从1字节开始
      再次假设内存读取粒度为4。
    • 图三:
      图三
      当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
      当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
    • 图四:
      一文读懂内存对齐的规则和作用第5张
      此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
      这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。
  • 内存对齐也可以认为是空间换时间。

免责声明:文章转载自《一文读懂内存对齐的规则和作用》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇关于ArcGis for javascript的使用linux开机自启动下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

扒一扒安卓渲染原理

导语:在测试流畅度的过程中,必不可免的要与FPS,Jank等指标接触,但为了加深理解,今天来简单扒一扒安卓的渲染原理;PerfDog使用Jank作为来代表游戏流畅度的指标,详情可以看APP&游戏需要关注Jank卡顿吗? 一.CPU与GPU结构 现在大部分移动端都会配有CPU(中央处理器)和GPU(图形处理器),有的现在还有一块NPU用于处理智能运算...

什么是内存(二):虚拟内存

通过上一篇文章的扯淡,我们应该已经明白了存储器的层次结构,技术细节很复杂,但是思想却不难理解,因为就是很简单的缓存思想。那么本文我们开始讨论关于内存的另一个话题.虚拟内存。其实思想也是很容易理解的。 我不知道有多少人听过虚拟内存这个概念,但是虚拟内存是计算机系统最重要的概念之一,并且它成功的主要原因就是它一直在沉默的,自动的工作,换句话说,我们这些做应用的...

psutil-获取系统性能信息模块

内容目录: 简介 psutil模块 安装 基本使用 获取系统总内存和使用内存 获取主机cpu核数和物理cpu个数 获取磁盘信息 获取网络信息 获取用户登录信息 获取系统开机时间 获取系统进程信息 简介 psutil 是一个垮平台库,能够轻松实现获取系统运行的进程和系统利用率(CPU、内存、磁盘、网络等)信息。通过它实现率同等命令行工具提供的功能,...

零拷贝

零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间。它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现 CPU 的零参与,彻底消除 CPU 在这方面的负载。实现零拷贝用到的最主要技术是 DMA 数据传输技术...

synchronized实现原理及其优化-(自旋锁,偏向锁,轻量锁,重量锁)

1.synchronized概述: synchronized修饰的方法或代码块相当于并发中的临界区,即在同一时刻jvm只允许一个线程进入执行。synchronized是通过锁机制实现同一时刻只允许一个线程来访问共享资源的。另外synchronized锁机制还可以保证线程并发运行的原子性,有序性,可见性。 2.synchronized的原理: 我们先通过反编...

【VS开发】#pragma pack(push,1)与#pragma pack(1)的区别

这是给编译器用的参数设置,有关结构体字节对齐方式设置, #pragma pack是指定数据在内存中的对齐方式。 #pragma pack (n)             作用:C编译器将按照n个字节对齐。 #pragma pack ()               作用:取消自定义字节对齐方式。 #pragma  pack (push,1)     ...