基本数据类型变量 结构体 union 类的内存字节对齐

来源:互联网 发布:净资产收益率排序软件 编辑:程序博客网 时间:2024/06/05 10:52

系原创,转载时请帖上地址:http://blog.csdn.net/qq51931373/article/details/45843975


什么叫内存字节对齐:就是在内存地址中按照一个规则(这个规则具体是什么在下面说)把一个变量放在它应该在的内存地址,而不是把变量从前到后一个紧挨着一个存放(那是 在理想状态下存在的,而理想和现实是有差距的,呵呵)。


为什么要字节对齐    :CPU从数据总线中读取内存中的数据,而CPU一次性从内存中读取多少位的数据是由数据总线的位数来决定的,拿i386处理器来说,它的数据总线是32位,也就决定了i386的处理器一次性是从内存中读取32位的数据(也就是4个字节)。如果在内存中的变量在存放的时候没有严格按照规则(下面会讲) 被存放到一个它应该在的内存地址,就会导致读取一个变量的值到CPU时,会读取多次,这就导致了效率上的下降,由于CPU的速度比内存的速度快很多倍,所以CPU从内存中读一次  和 从内存中读两次 从CPU的角度来说这个效率的下降是不可以接受的.而为什么没有对齐的话则会读两次甚至三次,请阅读这篇客:http://blog.csdn.net/qq51931373/article/details/45846001


 以下介绍基本数据类型变量  结构体  类对象的字节对齐规则:

在介绍规则之前先明确以下概念:

1. 基本数据类型变量的自身对齐值 = sizeof(type) ,如int 类型变量的内存对齐值就是4。如果int a[10] 则变量a的自身对齐值仍然是sizeof(int),而不是sizeof(a)*10

2. 结构体变量 或 类对象的自身对齐值 = 结构体或者类中的所有数据成员中自身对齐值最大的那个值.而数据成员的自身对齐值是根据第一条得来的。

3.显示指定的对齐值 ,比如手动写pragma pack(4) 这个4就是指定的对齐值,不写这段代码则是采用编译器的默认对齐值.

4.基本数据类型变量的有效对齐值 = min(基本数据类型变量的自身对齐值,显示指定的对齐值或编译器默认对齐值)

   结构体变量的有效对齐值 = min(结构体变量的自身对齐值,显示指定的对齐值或编译器默认对齐值)

   类对象的有效对齐值  = min(类对象的自身对齐值,显示指定的对齐值或编译器默认对齐值)


所以:

规则1.基本数据类型变量存放的初始地址 就应该是基本数据类型变量的有效对齐值的整倍数 。

           比如: 假设默认对齐值或者指定对齐值是4 ,那么 :char c; int a[10]; 则a的初始地址一定是在c的初始地址往后移动4个字节,假设c的地址是0x00000000则a的地址就是       0x00000004,而0x00000004是a的有效对齐值的整倍数


规则2.结构体变量或者类对象所占内存字节数的多少应该是结构体变量或者类对象的有效对齐值的整倍数.

          比如:

#pragma  pack(8)typedef struct T{char c[10]; //本身长度1字节 __int64 d;  //本身长度8字节int e;  //本身长度4字节short f;  //本身长度2字节char g;  //本身长度1字节short h;  //本身长度2字节};

struct T t1;

sizeof(t1) = 40;

为什么是40?在下一一道来:假设结构体T的初始地址是0x00000000

==>先计算第一个成员变量c的初始地址.c的自身对齐值是sizeof(char) = 1.指定对齐值是8,取两者最小,则c的有效对齐值是1,所以c的初始地址必须是1的整倍数,而0x00000000是1的整倍数,所以c被放在0x00000000处,并占用0x00000000-0x00000009这10个位置.

==>然后计算成员变量d的初始地址.d的自身对齐值是sizeof(__int64)=8.指定对齐值是8,取两者最小,则d的有效对齐值是8,所以d的初始地址必须是8的整倍数,而c占用了前面10个位置,所以d只能从0x0000000A(对应十进制10)位置开始,但是0x0000000A不是8的整倍数,所以只能往后移,一直移动到0x00000010(对应十进制16),并占用0x00000010-0x00000017

==>然后计算成员变量e的初始地址.e的自身对齐值是sizeof(int) = 4,指定对齐值是8,取两者最小,则e的有效对齐值是4,所以e的初始地址必须是4的整倍数,因为d占用到了0x00000017这个位置,所以接下来空的位置是0x00000018(对应十进制24),而这个位置刚好是4的整倍数所以e就占用0x00000018-0x0000001b

==>然后计算成员变量f的初始地址.f的自身对齐值是sizeof(short) = 2,所以f的有效对齐值是2.f占用的地址区间应该是0x0000001c-0x0000001d

==>g占用的一个字节,它的初始地址就是0x0000001e

==>h占用的地址区间是0x00000020-0x00000021

到目前位置从0x00000000 - 0x00000021是结构体所有成员变量所占用的内存地址区间,一共34个字节。


那么这个结构体变量占用的内存真的就是34个字节么?

结构体的最终大小应该是结构体有效对齐值的整倍数,结构体有效对齐值 = 8,所以为了是8的倍数就必须往后面再填充6个字节,最终大小为40个字节。


类对象的字节对齐原则和结构体变量的字节对齐原则是一样的,所以就不多说了。

下面是一些关于这方面自己认为比较好的博客:

http://zhangyu.blog.51cto.com/197148/673792/

http://www.cnblogs.com/tujunyan/archive/2009/05/23/1487934.html

http://blog.csdn.net/qq51931373/article/details/45846001


有错误的地方欢迎指正。


_____________________________________________________华丽的分割线____________________________________________________________

union变量的数据成员在内存中是互斥排他存在的,意思就是同一时刻union变量的内存中只会存在一个数据成员的值,所以在编程过程中如果一个集合中的两个或者多个变量存在互斥的寓意则可以考虑使用union.

union的字节对齐 和 内存布局:

 只要记住两个原则就可以了:

第一:union的内存的大小是由成员变量中占用字节数最多的那个初步决定的(为什么是初步决定)。

             比如: union { char c[1024]; uint32 i;}则这个union的内存大小初步估计是1024,为什么是初步估计?因为最终大小还要受第二个原则的影响。

第二:第一原则中得到的初步大小   必须是    union中每一个数据成员(除开占用内存最多的那个数据成员)的有效字节对齐值(它如何得到参看上面的struct部分)的整倍数。

 

举两个例子:

 第一个例子:

           

typedef union MyUnion{char c[14];UINT32 i;};
sizeof(MyUnion) = 16;

为什么是16?c占用14所以初步大小是14,14是i的有效字节对齐值的整倍数吗?  i的有效字节对齐值是4=min(自身字节对齐值4,我的编译器默认字节对齐值8).。明显14不是4的倍数,所以往后移动2个字节当然就是16


第二个例子:

#pragma  pack(2)//指定对齐值2,把编译器默认字节对齐值覆盖掉typedef union MyUnion{char c[14];UINT32 i;};

sizeof(MyUnion) = 14;

为什么是14?c占用14所以初步大小是14,i的有效字节对齐值是2=min(自身字节对齐值4,指定字节对齐值2).14是2的倍数吗?当然是咯。



—————————————————————————————————华丽的分割线-----------------------------------------------------------------------------------


下面是几个稍微复杂的例子,对他们理解之后,那么字节对齐就彻底搞懂了。


第一个:

typedef struct Iner{char c;UINT32 Iner_Int;short s;}sIner;typedef struct Outer{UINT32 Outer_Int;__int64 i64 ;sIner inter;char c;}sOuter;

sizeof(sOuter) = 32;


第二个:

typedef struct{char a1;short a2;char a3;}st_a;typedef union{long a;st_a ta;}un_a;

sizeof(un_a) = 8;


就运用上面的原则就可以知道为什么结果是这样,如果还是不明白的,在我博客留言。


0 0
原创粉丝点击