代码改变世界

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

2009-11-02 14:54  cesc711  阅读(180)  评论(0编辑  收藏  举报

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

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。