一,内存对齐的三条规则

  1. 数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)
  2. 结构体作为成员,如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
  3. 结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐

二,注意事项:

  1. 数组在内存中存储时是分开存储的,char类型的数组每个元素是 1Byte,内存对齐时按照单个元素进行对齐
  2. union(联合体)类型中的数据共用内存,联合的所有成员共用一段内存空间,存储地址的起始位置都相同,一般来说最大成员的内存宽度作为union的内存大小,主要的原因是为了节省内存空间,默认的访问权限是公有的,但是它同样要遵守内存对齐的原则,特别是第3条规则
  3. C++中空结构体占用 1Byte
  4. C++中空类同样是占用 1Byte的内存空间
  5. 类和结构体一样,需要内存对齐

三,举例说明

示例1:

struct Test1 { int a; double b; char c; };//24

类型

int

double

char

地址

0~3

8~15

16

累计所占内存(字节)

4

16

17

按照规则(最大成员长度整数倍)

4

16

24

解释:

  • int a; 占用 4Byte(存储位置0-3),规则1
  • double b; 占用 8Byte(存储位置是从该类型长度(也就是 8Byte)或整数倍开始存储8-15),规则1
  • char c; 占用 1Byte(存储位置16),规则1
  • 这时一共用了17 Byte,但是 sizeof 所得的大小为24,这就用到了第3条规则,最后sizeof的大小还必须是内部最大成员长度的整数倍,不足的要补齐,这个结构体中最大成员是double b; 8 Byte,最后sizeof的大小为24,规则3

示例2

struct Test2 { int a; double b; char c[6]; };//24

类型

int

double

char [6]

地址

0~3

8~15

16~21

累计所占内存(字节)

4

16

21

按照规则(最大成员长度整数倍)

4

16

24

解释:

  • int a; 占用 4Byte(存储位置0-3),规则1
  • double b; 占用 8Byte(存储位置是从该类型长度(也就是 8Byte)或整数倍开始存储8-15),规则1
  • 数组在内存中存储时是分开存储的,char类型的数组每个元素是 1Byte,按单个元素进行内存对齐,故sizeof大小还是24,注意1 & 规则3。所以下面的Test22的大小也是24 bytes。

示例4 

struct Test { int a; double b; char c; }; struct Test3 { int a; Test d; double b; char c; };//48

类型

int

int

double

char

double

char

地址

0~3

8~11

16~23

24

32~39

40

累计所占内存(字节)

4

12

24

25

40

41

按照规则(最大成员长度整数倍)

48

解释:

  • int a; 占用 4Byte(存储位置0-3),规则1
  • Test中最大的元素是double b; 占用 8Byte,Test中的成员是按照 8Byte 的整数倍的地址开始存储的,Test中int a; 占用 4Byte(存储位置8-11),double b; 占用 8Byte(存储位置16-23),char c; 占用 1Byte(存储位置24),规则2
  • double b; 占用 8Byte(存储位置32-39),规则1
  • char c; 占用1 Byte(存储位置40),40是最大元素大小8的整数倍,但是要大于40,按照规则3补齐,sizeof为48,规则1 & 规则2 & 规则3

示例4 

struct Test { int a; double b; char c; }; struct Test3 { int a; Test d; char c; };//40

类型

int

int

double

char

char

地址

0~3

8~11

16~23

24

32

累计所占内存(字节)

4

12

24

25

33

按照规则(最大成员长度整数倍)

24

32

40

解释:

  • Test3中的最大数据成员大小比成员结构体Test内部最大成员大小要小,这时规则3是按照成员结构体内部的最大成员的整数倍进行补齐的,sizeof的结果是40

四,联合体(union

找到联合体中最大的数据类型,还必须满足是所有成员的整数倍

union Test{ char a[20]; int b; float c; };

sizeof的大小是20,即a[20]的大小,同样20是b和c的倍数,规则3

union Test{ char a[20]; int b; float c; double d; };

sizeof的大小是24,即满足容下a[20],同样24是b、c和d的倍数,规则3

五,字节对齐的原因

  1. 平台原因(移植原因),不是所有的硬件平台都能任意访问地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
  2. 性能原因,经过内存对齐后,CPU的访问效率会得到很大的提高(CPU把内存当成是一块一块的,块的大小可以是2,4,8,16Byte 大小,因此CPU在读取内存时是一块一块进行读取的,当读取块的大小是 4Byte 时,一个数据所占的字节偏移(offset)为3|4|5|6,那么CPU访问数据时便需要访问两次,才能得到完整的数据,经过内存对齐后,便可以通过一次访问CPU获取完整的数据

六,用union区分大小端

到这了我们来想一个问题:如何用union区分大小端,首先我们先来了解一下什么是大小端:

比如一个short类型的变量:对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。

假如现有一32位int型数0x12345678,那么其MSB(Most Significant Byte,最高有效字节)为0x12,其LSB (Least Significant Byte,最低有效字节)为0x78,在CPU内存中有两种存放方式:(假设从地址0x4000开始存放)

1)大端模式:高位存在低地址

低地址 -----------------> 高地址

0x12 | 0x34 | 0x56 | 0x78

2)小端模式:低位存在低地址

低地址 ------------------> 高地址

0x78 | 0x56 | 0x34 | 0x12

实例:

#include<stdio.h> 

union CC { char a[4]; int b; int c; }; 

int main() 
{ 
    union CC m_cc; 
    m_cc.a[0] = 0x01; 
    m_cc.a[1] = 0x02; 
    m_cc.a[2] = 0x03; 
    m_cc.a[3] = 0x04; 
    printf("%xd\n",m_cc.b); 
}//输出04030201小端对齐 用16进制去看 01在最低位

解释:

  • int a中0x01存在起始位也就是最低位,后面地址增加
  • int b和int a公用内存,所以内容相同,16进制打印输出为04030201,即小端对齐
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐