项目开发过程中出现的错误二

昨天在调试程序的时候出现了一个问题,有这样一个结构体:

typedef struct _ip_statistic_item { v_ushort_16 header; v_uint_32 ip_addr; v_short_16 l5_protocol; v_ushort_16 up_total_speed:10, up_total_unit:6; v_ushort_16 up_telecom_speed:10, up_telecom_unit:6; v_ushort_16 down_total_speed:10, down_total_unit:6; v_ushort_16 down_telecom_speed:10, down_telecom_unit:6; } ip_statistic_item; 

 

在sizeof的时候,一直以为结果会是16,但是没想到结果却是20,一下子有点糊涂。后来才知道是由于内存没有对齐的问题,于是在网上找了些资料看看。

对于struct和union来说,第一个成员位于offset为0的位置。以后的每一个成员的偏移量,必须是以下两个数中比较小的那一个,第一个是#pragma pack(n),这个是编译器的一个预处理指令,代表设置为n字节对齐(vc6默认的为8字节对齐,gcc和g++的还不清楚,应该也为8或者16)。第二个是该成员的自身长度。

这样的话,来看上面的例子(这里当作g++是16字节对齐或8字节对齐是一样的):

第一个成员header占用2个字节,放置位置为0-1。
第二个成员ip_addr本身长度为4个字节,小于编译器对齐长度,所以ip_addr的offset应该是4的整数倍,编译器会在header后面添加两个额外字节,ip_addr放置位置为4-7。
第三个成员l5_protocol为2个字节,放置位置为8-9。后四个成员的放置位置为10-17。

到这里已经占用了18个字节。那么为什么是20个字节呢?原来除了每个成员要对齐之外,结构体本身也要进行内存对齐。这时候的对齐规则也要参见两个数,取比较小的那一个,第一个还是#pragma pack(n),第二个是结构题中最长成员的长度。在这里n=16或8,最长成员长度是v_uint_32为4,所以结构体的大小应该是4的整数倍。于是最后在结构体的结尾还要额外添加两个字节,从而达到20个字节。

为什么要进行内存对齐呢?因为CPU读取内存不是按照一个字节一个字节来读取的,它一次读取一个内存块,比如4个字节32位。如果不进行内存对齐的话,那ip_addr的存放位置是2-5,这样CPU要先访问一次内存,把内存0-3放入寄存器中,然后再访问内存,把4-7放入寄存器中,然后把0,1,6,7的数据剔除掉,然后把2,3,4,5的合并起来,才能得到ip_addr。如果内存已经对齐,那么CPU直接将4-7的位置放入寄存器就可以了,大大减少了消耗。

下面是一道intel和微软的面试题:

#pragma pack(8) struct s1{ short a; long b; }; struct s2{ char c; s1 d; long long e; }; #pragma pack() 

 

1.sizeof(s2)=?

2.s2的c后面空了几个字节是d? 

现在我来分析一下这个题目:首先设定了编译器为8字节对齐,分析s1,short类型占用2个字节,long类型占用4个字节,且按照4字节对齐,所以s1的大小为8个字节。再来分析s2,char类型的c占用一个字节,现在遇到一个结构嵌套的问题:s1占用8个字节,编译器为8字节对齐,但是不能将s2中的s1按照8字节对齐,应该根据s1中最长数据成员的长度和pack(8)做比较,采取较小的那一个。所以s1为4字节对齐,它在内存中的位置为4-11。然后一个long long类型按照8字节对齐,占用位置为16-23。所以答案已经出来了,sizeof(s2)=24,s2的c后面空了3个字节是d。 

涉及位域的对齐
1. 类型相同,而且相邻位域字段位宽加起来不大于类型的位宽时,下一个字段紧接着前一个字段。
2. 类型相同,但是相邻位域字段位宽加起来大于类型的位宽,下一个字段从新的存储单元开始,偏移量为字段类型的整数倍。
3. 类型不同,不同的编译器有不同的做法。VC6选择不压缩方式,即在新的存储单元存放。而GCC在上一个字段的剩余位宽与下一个字段的位宽之和不跨越字节的情况下,采取压缩方式,在紧接着的存储单元存放。(此时也要遵守偏移量为类型大小的整数倍这一原则)
例:struct S { char c1 : 4; char c2 : 4; unsigned short us1 : 13; char c4 : 1; }; c1的存放位是第1个字节的0-3位,c2按照规则1, 存放位置的第1个字节的4-7位。us1按照规则3, 存放在第3个字节的0-7位以及第4个字节的0-4位。c4按照规则3,如果编译器是VC6,则存放在第5个字节的0位。如果编译器是GCC,则存放在第4个字节的第5位。所以在VC6之下sizeof(S) = 6, 在GCC之下, sizeof(S) = 4。struct S { char c1 : 4; char c2 : 4; unsigned short us1 : 13; char c4 : 7; // c4占7位 }; 如果修改c4的位域,即:在GCC编译器下,从第4个字节的第5位开始,存放不下c4,出现夸字节的情况。则c4应该存储在新的字节中,同时遵守偏移量是类型的整数倍的原则。所以此时在GCC之下, sizeof(S) = 6;

 

你可能感兴趣的:(项目开发过程中出现的错误二)