您的位置:首页 > 其它

字对齐之 sizeof和pragma pack 的用法

2010-08-21 20:16 357 查看
面试常问的几道题
1. sizeof与 #pragma pack 用法
(注:以下用法都以windows平台下32位机为标准。)
sizeof与 #pragma pack 的用法经常会在面试或笔试中问到,估计很多人也和我一样,答错的几率很大。究其原因,还是不明白原理。有时候只是凭着笔试前的记忆乱猜的。
首先,我们看sizeof的用法。
对于结构体:
struct Data1
{
char m_ch;
int m_nData;
};
sizeof(Data1)=?
a) 可能有人会回答:sizeof(Data1) = 1 + 4=5;很明显,这类人肯定是连字对齐是什么都不知道。
b) 也有人会这么回答:sizeof(Data1) = 2 + 4=6;这类人可能知道什么是字对齐,但是不明白到底字对齐是啥意思。可能认为字对齐就是以2的整数倍对齐。结果面试官一问为什么,想当然的就说出了自己的原因,【说32位机器的字对齐方式是这样的,结构体中每个字段的大小都必须是2的整数倍,因为这样CPU寻址会快】,并毫不犹豫的认为这种说法很完美。但实际上,这只是答对了一点皮毛。很悲剧,我就是属于这类人,只知道有这么回事,但并不知道怎么回事。
经过面试官的指点与自己google出来的结果,我进行了一些总结。我们还是从上面的例子开始讲起。这里先给个链接,我当中有很大一部分是根据这个总结而来的。
http://www.cnblogs.com/bingxuefly/archive/2007/11/12/957056.html
n Windows平台下,默认的字对齐方式遵循以下2条规则:
条款1 成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类
型所占用的字节数的倍数。
也就是说,要遵循这么个法则。
------------------------------------------------------------------------------

成员变量类型 | 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)


char | 偏移量必须为sizeof(char), 即1的倍数
short | 偏移量必须为sizeof(short), 即2的倍数
int | 偏移量必须为sizeof(int), 即4的倍数
float | 偏移量必须为sizeof(float), 即4的倍数
long | 偏移量必须为sizeof(long), 即4的倍数
double | 偏移量必须为sizeof(double), 即8的倍数
__int64 | 偏移量必须为sizeof(__int64), 即8的倍数
…….
条款2 各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。
条款3 整个结构体的大小必须是结构体中所占空间最大的那个类型所占用字节数的整数倍。
分析:据这两条规则,对于结构体Data1,我们做如下分析。
【注意,偏移大小从0开始,也就是说,结构体的第一个字节我们认为是偏移为0】
首先,编译器会为char m_ch分配空间,其起始地址跟结构的起始地址相同,根据条款1,此时相对偏移为0,正好为sizeof(char)=1的倍数。然后为int m_nData分配空间,由于它的相对偏移要是sizeof(int)=4的倍数,显然,其相对偏移就是4了,那么,根据条款2,相对偏移为1,2,3的字节就应该空着,里面什么内容也不被填充。
再来看条款3满不满足,此时结构体的大小已为8个字节,恰好是结构体里面最大的数据段【int m_nData】大小的整数倍。
所以,整个数据的大小就为sizeof(Data1) = 1+3+4=8;
也就是sizeof(Data1) = sizeof(char) + 自动对齐的3个字节 + sizeof(int) = 8;
再来分析一个更加复杂的结构:
struct Data
{
__int64 m_64n1;
short m_sh1;
__int64 m_64n2;
short m_sh2;
char m_ch;
short m_sh3;
};
第一步,编译器会为__int64 m_64n1分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(__int64)=8的整数倍,此时相对偏移为0【也就是结构体的起始位置】,正好是8的整数倍。此字段占用的字节为第0~7个字节。
第二步,编译器会为short m_sh1分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(short)=2的整数倍,根据第一步分配后的空间来看,此时相对偏移已经移动到第8个字节,正好是其整数倍。此字段占用的字节为第8~9个字节。
第三步,编译器会为__int64 m_64n2分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(__int64)=8的整数倍,根据第二步分配后的空间来看,此时相对偏移已经移动到第10个字节,不是其整数倍。那么此时就要根据条款2,将偏移为第10~15的位置填充。此时偏移为16,正好是8的整数倍。此字段占用的字节为第16~23个字节。
第四步,编译器会为short m_sh2分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(short)=2的整数倍,根据第三步分配后的空间来看,此时相对偏移已经移动到第24个字节,正好是其整数倍。此字段占用的字节为第24~25个字节。
第五步,编译器会为char m_ch分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(char)=1的整数倍,根据第四步分配后的空间来看,此时相对偏移已经移动到第26个字节,正好是其整数倍。此字段占用的字节为第26个字节。
第六步,编译器会为short m_sh3分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(short)=2的整数倍,根据第二步分配后的空间来看,此时相对偏移已经移动到第27个字节,不是其整数倍。那么此时就要根据条款2,将偏移为第27的位置填充。此时偏移就为28了,正好是2的整数倍。此字段占用的字节为第28~29个字节。
此时,整个结构体大小就为30,那么,根据条款3,整个结构体中最大的成员类型(或者是成员变量)的大小为8,因此,结构体最终大小应该为32,第30~31个字节自动被填充。
可以定义一个结构体,看一下其内存结构:
Data5 data5 = {1, 2, 3, 4, 5, 6};
01 00 00 00 00 00 00 00 02 00 cc cc cc cc cc cc 03 00 00 00 00 00 00 00 04 00 05 cc 06 00 cc cc
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
上面的一行为对应偏移。

现在,我们看#pragma pack的用法了。
使用格式:
#pragma pack(push) // 保存对齐状态
#pragma pack(4) // 设定为4字节对齐
struct Data4
{
char m_ch;
short m_sh;
double m_nData;
};
#pragma pack(pop) // 恢复对齐状态
这里我直接把人家的抄过来了,人家写得很清楚了。
#pragma pack(n)来设定变量以n字节对齐方式。
ü n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一, 如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;
第二, 第二,如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
ü 结构的总大小也有个约束条件,分下面两种情况:
如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;
需要注意的一个问题是, n的大小只能为为1、2、4、8、16,如果写其它的,编译器会报出警告【warning C4086: expected pragma parameter to be '1', '2', '4', '8', or '16'】

好了,我们来分析一下结构体的大小如何求得。
第一步,编译器会为char m_ch分配空间,根据n=4,而sizeof(char)=1,那么偏移量要满足默认对齐方式,此时相对偏移为0,正好是其的整数倍。此字段占用的字节为第0个字节。
第二步,编译器会为short m_sh分配空间,根据n=4,而sizeof(short)=1,那么偏移量要满足默认对齐方式,此时相对偏移为1,不是其的整数倍。根据先前默认的方式,需要将相对偏移为第1个字节的位置填充起来,然后再为该字段分配空间。此字段占用的字节为第2~3个字节。
第二步,编译器会为double m_nData分配空间,根据n=4,而sizeof(double)=8,n<8,那么偏移量只要是4的倍数就可以了,此时相对偏移为4,刚好是n的整数倍。所以,此字段占用的字节为第4~11个字节。
【再次提醒一下,占用字段的字节我们按照第0个开始计算,也就是说,如果一个结构体占用32个字节,那么我们数数的时候为第0到第31个字节来算;这里的偏移也是从0开始计算】

还有一些重要的信息我也抄过来了。
sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,
第一,结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。
第二,没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一个实例在内存中都有唯一的地址。
参考原文链接:http://www.cnblogs.com/bingxuefly/archive/2007/11/12/957056.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: