内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方式,包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐 。

为什么需要内存对齐?对齐有什么好处?是我们程序员来手动做内存对齐呢?还是编译器在进行自动优化的时候完成这项工作?

在现代计算机体系中,每次读写内存中数据,都是按字(word,4个字节,对于X86架构,系统是32位,数据总线和地址总线的宽度都是32位,所以最大的寻址空间为232 = 4GB(也 许有人会问,我的32位XP用不了4GB内存,关于这个不在本篇博文讨论范围),按A[31,30…2,1,0]这样排列,但是请注意为了CPU每次读写 4个字节寻址,A[0]和A[1]两位是不参与寻址计算的。)为一个块(chunks)来操作(而对于X64则是8个字节为一个快)。注意,这里说的 CPU每次读取的规则,并不是变量在内存中地址对齐规则。既然是这样的,如果变量在内存中存储的时候也按照这样的对齐规则,就可以加快CPU读写内存的速 度,当然也就提高了整个程序的性能,并且性能提升是客观,虽然当今的CPU的处理数据速度(是指逻辑运算等,不包括取址)远比内存访问的速度快,程序的执 行速度的瓶颈往往不是CPU的处理速度不够,而是内存访问的延迟,虽然当今CPU中加入了高速缓存用来掩盖内存访问的延迟,但是如果高密集的内存访问,一 种延迟是无可避免的,内存地址对齐会给程序带来了很大的性能提升。

内存地址对齐是计算机语言自动进行的,也即是编译器所做的工作。但这不意味着我们程序员不需要做任何事情,因为如果我们能够遵循某些规则,可以让编译器做得更好,毕竟编译器不是万能的。

为了更好理解上面的意思,这里给出一个示例。在32位系统中,假如一个int变量在内存中的地址是0x00ff42c3,因为int是占用4个字节,所以它的尾地址应该是0x00ff42c6,这个时候CPU为了读取这个int变量的值,就需要先后读取两个word大小的块,分别是0x00ff42c0~0x00ff42c3和0x00ff42c4~0x00ff42c7,然后通过移位等一系列的操作来得到,在这个计算的过程中还有可能引起一些总线数据错误的。但是如果编译器对变量地址进行了对齐,比如放在0x00ff42c0,CPU就只需要一次就可以读取到,这样的话就加快读取效率。

1、基本数据对齐
                 在X86,32位系统下基于Microsoft、Borland和GNU的编译器,有如下数据对齐规则:
                 a、一个char(占用1-byte)变量以1-byte对齐。
                 b、一个short(占用2-byte)变量以2-byte对齐。
                 c、一个int(占用4-byte)变量以4-byte对齐。
                 d、一个long(占用4-byte)变量以4-byte对齐。
                 e、一个float(占用4-byte)变量以4-byte对齐。
                 f、一个double(占用8-byte)变量以8-byte对齐。
                 g、一个long double(占用12-byte)变量以4-byte对齐。
                 h、任何pointer(占用4-byte)变量以4-byte对齐。

而在64位系统下,与上面规则对比有如下不同:
                 a、一个long(占用8-byte)变量以8-byte对齐。
                 b、一个double(占用8-byte)变量以8-byte对齐。
                 c、一个long double(占用16-byte)变量以16-byte对齐。
                 d、任何pointer(占用8-byte)变量以8-byte对齐。

2、结构体数据对齐
       结构体数据对齐,是指结构体内的各个数据对齐。在结构体中的第一个成员的首地址等于整个结构体的变量的首地址,而后的成员的地址随着它声明的顺序和实际占用的字节数递增。为了总的结构体大小对齐,会在结构体中插入一些没有实际意思的字符来填充(padding)结构体。

在结构体中,成员数据对齐满足以下规则:
        a、结构体中的第一个成员的首地址也即是结构体变量的首地址。
        b、结构体中的每一个成员的首地址相对于结构体的首地址的偏移量(offset)是该成员数据类型大小的整数倍。
        c、结构体的总大小是对齐模数(对齐模数等于#pragma pack(n)所指定的n与结构体中最大数据类型的成员大小的最小值)的整数倍。

7:  struct

8:  {
   9:      char a;
  10:      int b;
  11:      short c;
  12:      char d;
  13:  }dataAlign;
  14:   
  15:  struct
  16:  {
  17:      char a;
  18:      char d;
  19:      short c;
  20:      int b;
  21:      
  22:  }dataAlign2;

仔细观察,会发现虽然是一样的数据类型的成员,只不过声明的顺序不同,结构体占用的大小也不同,一个8-byte一个12-byte。为什么这样,下面进行具体分析。  
       首先来看dataAlign2,第一个成员的地址等于结构体变量的首地址,第二个成员char类型,为了满足规则b,它相对于结构体的首地址的偏移量必须 是char=1的倍数,由于前面也是char,故不需要在第一个和第一个成员之间填充,直接满足条件。第三个成员short=2如果要满足规则b,也不需 要填充,因为它的偏移量已经是2。同样第四个也因为偏移量int=4,不需要填充,这样结构体总共大小为8-byte。最后来验证规则c,在VC中默认 的#pragma pack(n)中的n=8,而结构体中数据类型大小最大的为第四个成员int=4,故对齐模数为4,并且8 mode 4 = 0,所以满足规则c。这样整个结构体的总大小为8。

对于dataAlign,第一个成员等于结构体变量首地址,偏移量为0,第二个成员为int=4,为了满足规则b,需要在第一个成员之后填充3-byte,让它相对于结构体首地址偏移量为4,结合运行结果,可知&dataAlign.a = 0x01109140,而&dataAlign.b = 0x01109144,它们之间相隔4-byte,0x01109141~0x01109143三个字节被0填 充。第三个成员short=2,无需填充满足规则b。第四个成员char=1,也不需要填充。结构体总大小相加4 + 4 + 2 + 1 = 11。同样最后需要验证规则c,结构体中数据类型大小最大为第二个成员int=4,比VC默认对齐模数8小,故这个结构体的对齐模数仍然为4,显然11 mode 4 != 0,故为了满足规则c,需要在char后面填充一个字节,这样结构体变量dataAlign的总大小为4 + 4 + 2 + 2 = 12。

[转]C++结构体|类 内存对齐详解相关推荐

  1. 3.c语言结构体成员内存对齐详解

    一.关键一点 最关键的一点:结构体在内存中是一个矩形,而不是一个不规则形状 二.编程实战 1 #include <stdlib.h> 2 #include <stdio.h> ...

  2. 自定义类型详解:结构体(内存对齐、位段) + 枚举 + 联合

    目录 一.结构体 1.特殊的声明 2.结构体自引用 3.结构体变量的定义和初始化 4.打印结构体 二.==结构体内存对齐== 1.内存对齐 结构体嵌套如何求 为什么存在内存对齐? 2.修改默认对齐数 ...

  3. c 定义结构体时提示应输入声明_C++|了解结构体的内存对齐(成员声明的顺序影响占用空间大小)...

    我们使用的电子计算机绝大部分都是冯·诺依曼结构的机器,遵循"存储程序"的概念.数据处理以存储为前提,在编程中数据如何"存得进去,取得出来",并且符合空间.时间效 ...

  4. C语言:指针的偏移步长、结构体成员的偏移量、嵌套结构体成员的偏移量、结构体的内存对齐

    文章目录 1 不同类型指针的偏移步长 2 结构体成员的偏移量 3 嵌套结构体成员的偏移量 4 结构体的内存对齐 4.1 内存对齐的原因与优点 4.2 结构体内存对齐的规则 4.3 结构体嵌套结构体时的 ...

  5. 结构体的内存对齐规则

    1.结构体的内存对齐规则 1.第一个成员在与结构体变量偏移量为0的地址处. 2.其他成员变量都放在对齐数(成员的大小和默认对齐数的较小值)的整数倍的地址处. 对齐数=编译器默认的一个对齐数与该成员大小 ...

  6. 【C语言进阶】结构体 位段 枚举 联合体详解

    目录 1.结构体 1.1结构体的基础类型 1.2结构的声明 1.3特殊的声明 1.4结构体的自引用 1.5结构体变量的定义和初始化 1.6结构体内存对齐(计算结构体的大小) 1.6.1 结构体对齐规则 ...

  7. 结构体知识点内存对齐位段、枚举、联合

    ***结构体类型创建*** ***结构体初始化*** struct Stu{ char name[20]; int age; char sex[5];char id[20]; } Stu1; 在这里S ...

  8. 字节对齐《c和指针》笔记--包含位域结构体的内存对齐(32bit,GCC)

    最近使用开发的过程中出现了一个小问题,顺便记录一下原因和方法--字节对齐 C99划定int.unsigned   int和bool可以作为位域类型.但编译器几乎都对此作了扩展,答应其它类型类型的存在. ...

  9. C语言--结构体与内存对齐准则简单理解

    平时基本不涉及这个问题,被问起才发现没那么简单... 一,结构体:内存中所有类型字节之和 #include<stdio.h> struct icd {int a;char b;double ...

最新文章

  1. 祝贺《WCF邮件通信系统》在高阳市场研究汇编第五期发表
  2. SAP 错误日志的调查
  3. python基础-C扩展
  4. mysql云数据库 磁盘利用率_云数据库MySQL参数的那些事儿
  5. 中断函数 printf_嵌入式中断服务函数的一些特点
  6. 【FLink】Flink 消费 kafka 消费组 死掉 Marking the coordinator dead for group 造成数据重复消费
  7. Javascript获取For循环所用时间
  8. 如何实现ArcSDE的集群功能
  9. Power Query Advanced Editor键盘快捷键
  10. c语言程序设计题怎么写,C语言程序设计题库1(最新整理)
  11. (1) IFC格式简介 (Industry Foundation Class)
  12. 如何写好PRD文档?
  13. fckeditor for php 下载,FCKeditor(HTML在线编辑器)下载_FCKeditor(HTML在线编辑器)官方下载-太平洋下载中心...
  14. 使SplitContainer中某个Panel宽度、高度不变
  15. MyEclipse10破解工具,crack下载
  16. 定制自己的xDoclet标签
  17. 用户正确使用趋势跟随的交易系统的方法
  18. 区块链10年兴衰录:中国是最好的发展土壤
  19. [BZOJ4327] JSOI2012玄武密码
  20. 【伊利丹】Hadoop-2.5.0-CDH5.2.0/Hive与Hbase整合实验

热门文章

  1. java log 配置,java日志系统--log4j配置解析过程,源码分析
  2. java bigdecimal乘法_Java BigDecimal类型的 加减乘除运算
  3. 循环卷积和周期卷积的关系_PSConv:多位一体、即插即用卷积单元
  4. yii2手册_史上最全的技术手册整理总结,编程小白都从这篇文章迅速成为大牛...
  5. java 数组 包含_Java中高效的判断数组中某个元素是否存在详解
  6. matlab循环前后变量定义,Matlab for 多个变量循环能不能这样啊 ,求教高手!!!!...
  7. BP神经网络 PID控制simulink仿真
  8. Linux json格式化输出
  9. java中正则全局匹配_JS中正则表达式全局匹配模式/g用法实例
  10. linux内核匹配的编译器gcc,linux下正则匹配的C实现