81287
理论上来说,结构体A与B的大小应该都是一样的,造成这种原因的就是字节对齐引起来的。
2.为什么要字节对齐
为什么呢?简单点说:为了提高存取效率。字节是内存空间分配的最小单位, 在程序中,我们定义的变量可以放在任何位置。其实不同架构 的CPU在访问特定类型变量时是有规律的,比如有的CPU访问int型变量时,会从偶数地址开始读取的,int类型占用4个字节(windows平台)。 0X0000,0X0004,0X0008.....这样只需要读一次就可以读出Int类型变量的值。相反地,则需要读取二次,再把高低字节相拼才能得到 int类型的值,这样子看的话,存取效率当然提高了。 通常写程序的时候,不需要考虑这些情况,编译都会为我们考虑这些情况,除非针对那些特别架构的 CPU编程的时候的则需要考虑 。当然用户也可以手工控制对齐方式。
3.编译器对字节对齐的一些规则
我从下面三条说明了编译器对字节处理的一些原则。当然除了一些特殊的编译器在处理字节对齐的方式也不一样, 这些情况我未碰到过,就不作说明了。
a.
关于数据类型自身的对齐值,不同类型会按不同的字节来对齐。
类型 |
对齐值(字节) |
char |
1 |
short |
2 |
int |
4 |
float |
4 |
double |
8 |
b.
类、结构体的自身对齐字节值。对于结构体类型与类对象的对齐原则:使用成员当中最大的对齐字节来对齐。比如在Struct A中,int a的对齐字节为4,比char,short都大,所以A的对齐字节为4
c.
指定对齐字节值。意思是指使用了宏 #pragma pack(n)来指定的对齐值
d. 类、结构及成员的有效对齐字节值。有效对齐值=min(类/结构体/成员的自身对齐字节值,指定对齐字节值)。
有效对齐值决定了数据的存放方 式,sizeof 运算符就是根据有效对齐值来计算成员大小的。简单来说, 有效对齐其实就是要求数据成员存放的地址值能被有效对齐值整除,即:地址值%有效对齐值=0
4. 结合编译器分析示例
根据上面的原则,分析Struct A的size。结构体的成员内存分配是按照定义的顺序来分析的。
structA
{
inta;
charb;
shortc;
}
为了简单起见, 我假设Struct A存取的起始地址为 0x0000 在没有指定对齐值的情况下,分析步骤:
step 1: 根据第二条,首先为结构体选择对齐值:选择成员中最大的对齐值,即int a,对齐值为4
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,分析步骤如下:
structB
{
chara;
intb;
shortc;
}
step 1: 确实结构体B对齐值:选择成员中最大的对齐值,即int a,对齐值为4
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的定义:
#pragmapack(2)
structC
{
chara;
intb;
shortc;
};
在代码中,手工指定了对齐值为2个字节,分析步骤如下:
step 1: 确定结构体C对齐值:选择成员中最大的对齐值,即int a,对齐值为4
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个字节。
最后分析#pragmapack(1)这种情况,这种情况非常简单,对齐值为1,因为1可以被任何数据整除,所以Struct D的成员变量存取顺序是连续的,这样就好办了,sizeof(D)=sizeof(int)+sizeof(char)+sizeof(short)=4+1+2=7 (比如从0x0000~0x0006)
总结
在考虑字节对齐时要细心,搞清楚几个重要的概念,如类型自身对齐值,手工对齐值以及有效对齐值,有效对齐值决定了最后的存取方式,有效对齐值等于类型自身对齐值与手工对齐值中较小的一个。理解了这一点,对sizeof运算符对类型或都结构的运算也彻底明白了。
以下测试实例 -------------------------------------------------------------------------------------------------
#include <iostream>
using namespace std;
// 没有指定对齐字节,则使用 结构体或类 中字节最大的类型字节值(前提是不超过 8 字节,当超过8字节,则采用4字节对齐)
struct A // 8 ->(8%4 == 0)-> 8
{
int a; //4
char b;
short c;
};
struct B // 10 ->(10%4 != 0)-> 12
{
char a;
int b; // 4
short c;
};
struct C // 12 ->(12%8 != 0)-> 16
{
double b; // 8
char a;
short c;
};
struct D // 18 ->(18%8 != 0)-> 24
{
char a;
double b; // 8
short c;
};
struct E // 20 ->(20%4 == 0)-> 20
{
char a;
long double b; // (12 > 8) -> 采用 4 字节对齐, 和 #pragma pack(4) 效果一样
short c;
};
struct F // 16 ->(16%4 == 0)-> 16
{
long double b; // 12
char a;
short c;
};
#pragma pack(4) // 和 struct E 效果一样
struct G // 20 ->(20%4 == 0)-> 20
{
char a;
long double b; // 12
short c;
};
#pragma pack(2) // 会影响后续类型的对齐有效值,直到重新设置
struct H // 8 ->(8%2 == 0)-> 8
{
char a;
int b;
short c;
};
//#pragma pack(2)
struct I // 12 ->(12%2 == 0)-> 12
{
char a;
double b; // 8
short c;
};
#pragma pack(1)
struct J // 7 ->(7%1 == 0)-> 7
{
int a;
char b;
short c;
};
int main()
{
long double bb;
cout << sizeof(bb) << endl; // 12
// 输出结果为: 8 12 16 24 20 16 20 8 12 7
cout << sizeof(A) << " "<< sizeof(B) << " "<< sizeof(C) << " "<< sizeof(D) << " "<< sizeof(E) << " "<< sizeof(F) << " "<< sizeof(G) << " "<< sizeof(H) << " "<< sizeof(I) << " "<< sizeof(J) << endl;
return 0;
}
优质内容筛选与推荐>>
1、struts2中s:select标签的使用 Map2、js前台改变服务器控件的disable的属性,后台获取不到值3、Java垃圾回收机制4、Linux kernel ‘uio_mmap_physical’函数缓冲区溢出漏洞5、入门常用SQL及官方文档的使用
长按二维码向我转账
受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。
阅读
好看
已推荐到看一看
你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
取消
分享想法到看一看
确定
最多200字,当前共字
微信扫一扫
关注该公众号