阅读指引:
-
示例代码
-
为什么要字节对齐
-
编译器对字节对齐的一些规则
-
结合编译器原则分析示例
-
总结
1.示例代码
先看一下这段程序的运行结果。
{
int a;
char b;
short c;
};
struct B
{
char a;
int b;
short c;
};
#pragma pack(2)
struct C
{
char a;
int b;
short c;
};
#pragma pack(1)
struct D
{
int a;
char b;
short c;
};
int _tmain( int argc, _TCHAR * argv[])
{
cout << sizeof (A) << " " << sizeof B << " " << sizeof C << " " << sizeof D << endl;
return 0 ;
}
理论上来说,结构体A与B的大小应该都是一样的,造成这种原因的就是字节对齐引起来的。
2.为什么要字节对齐
3.编译器对字节对齐的一些规则
我从下面三条说明了编译器对字节处理的一些原则。当然除了一些特殊的编译器在处理字节对齐的方式也不一样, 这些情况我未碰到过,就不作说明了。
类型 | 对齐值(字节) |
char | 1 |
short | 2 |
int | 4 |
float | 4 |
double | 4 |
d. 类、结构及成员的有效对齐字节值。有效对齐值=min(类/结构体/成员的自身对齐字节值,指定对齐字节值)。 有效对齐值决定了数据的存放方 式,sizeof 运算符就是根据有效对齐值来计算成员大小的。简单来说, 有效对齐其实就是要求数据成员存放的地址值能被有效对齐值整除,即:地址值%有效对齐值=0
4. 结合编译器分析示例
根据上面的原则,分析Struct A的size。结构体的成员内存分配是按照定义的顺序来分析的。
{
int a;
char b;
short c;
}
step 2 : 再根据第四条原则,决定有效对齐值:即然没有手工指定对齐值,则使用默认的值: 4 (windows 32平台)
step 3 : int a 的有效地址值 = min( 4 , 4 ),(因为0x0000 % 4 = 0 ),这样a的地址就是从 0X0000 ~ 0x0003
step 4 : char b 的有效对齐值 = min( 1 , 4 ),地址依次从0x0004 (因为Ox0004 % 1 = 0 )开始,分配一个字节,地址段分配情况就是: 0x0000 ~ 0x0004
step 5 : short c 的有效对齐值 = min( 2 , 4 ),理论上说,分配的地址应该是连续的(从0x0005 ~ 0x00006 ),但是由于要求考虑到对齐的情况,所求要求地址段 偏移,这样就从0x0006(Offset + 1 ,因为0x0006 % 2 = 0 )开始,分配2个字节的地址0x0006 ~ 0x0007 .
目前为止,地址段的分配情况就是: 0x0000 ~ 0x0007这样sizeof(A)的大小 = 0x0000 ~ 0x0007共8个字节大小,同时,8%4=0保证了Struct A的地址段与4成偶数倍。
接下来分析Struct B的大小,同样假设Struct B的起始地址为0x0000,分析步骤如下:
{
char a;
int b;
short c;
}
step 2 : 确定手工指定对齐值,使用默认的值: 4 (windows 32 , VC6.0平台)
step 3 : char a 的有效地址值 = min( 1 , 4 ),a的地址就是 0X0000 (因为0x0000 % 1 = 0 )
step 4 : int b 的有效对齐值 = min( 4 , 4 ),地址依次从0x0004 ~ 0x0007 (因为Ox0004 % 1 = 0 )开始,分配4个字节,目前j地址段分配情况就是: 0x0000 ~ 0x0007
step 5 : short c 的有效对齐值 = min( 2 , 4 ),c从0x0008 ~ 0x0009 (因为0x0008 % 2 = 0 )开始,偏移2个字节的地址0x0006 ~ 0x0007 .
至止,地址段的分配情况就是: 0x0000 ~ 0x0009共10个字节,但是Struct B的对齐值为4,这就要求地址地段再偏移2个字节,这样就是从0x0000 ~ 0x000B共12(因为12 % 4 = 0 )个字节大小。这样, sizeof (B) = 12
再来使用Pragma手工更改了字节对齐值的情况,先看看Struct C的定义:
struct C
{
char a;
int b;
short c;
};
在代码中,手工指定了对齐值为2个字节,分析步骤如下:
step 2 : 确定手工指定对齐值,使用手工指定的值: 2
step 3 : char a 的有效地址值 = min( 1 , 2 ),(因为0x0000 % 2 = 0 ),这样a的地址就是0x0000
step 4 : int b 的有效对齐值 = min( 4 , 2 ),地址依次从0x0002 ~ 0x0005 (因为Ox0002 % 2 = 0 )开始,分配4个字节,目前地址段分配情况就是: 0x0000 ~ 0x0005
step 5 : short c 的有效对齐值 = min( 2 , 2 ),由于要求考虑到对齐的情况,从0x0006(因为0x0006 % 2 = 0 )开始,分配2个字节的地址0x0006 ~ 0x0007
目前为止,地址段的分配情况就是: 0x0000 ~ 0x0007共8个字节,同时也保证了Struct C的对齐情况(2字节对齐,pragma(2)), sizeof (C) = 8
请注意这种情况与Struct B的情况有区别,B的sizeof大小为12个字节,C的sizeof大小为8个字节。
最后分析#pragma pack(1)这 种情况,这种情况非常简单,对齐值为1,因为1可以被任何数据整除,所以Struct D的成员变量存取顺序是连续的,这样就好办 了,sizeof(D)=sizeof(int)+sizeof(char)+sizeof(short)=4+1+2=7 (比如从0x0000~0x0006)
总结
在考虑字节对齐时要细心,搞清楚几个重要的概念,如类型自身对齐值,手工对齐值以及有效对齐值,有效对齐值决定了最后的存取方式,有效对齐值等于类型自身对齐值与手工对齐值中较小的一个。理解了这一点,对sizeof运算符对类型或都结构的运算也彻底明白了。
From: http://repository.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。