c--浅析结构体的大小

 
 
 
tag>内存对齐 结构体大小 sizeof</tag>

前几天为了做ppt,而写了一个处理bmp文件的小程序,没想到一个小程序,竟然忙乎了我半天。

最后才发现,我是栽到了结构体在内存对齐的问题上。

比如说,下面这个结构体,用sizeof函数,得到的结果是12.为什么呢?

typedef struct _C
{
char a;
int b;
char c;
}C;

这是因为x86系统下,结构体会发生内存对齐操作,这是为了cpu存取数据速度快而设定的。

在上述的结构体中,块头最大的是int b,占4个字节,所以整个结构体就占12个字节。

typedef struct _D
{
char a;
double b;
char c;
}D;

而上面的结构体根据块头最大原则,就是得出sizeof(D)=24。呵呵,实际上的程序也证实了这一点。

对于我遇到的结构体,具体定义如下:

typedef struct tagBITMAPFILEHEADER {
                            WORD                        bfType;
                            DWORD                       bfSize;
                            WORD                        bfReserved1;
                            WORD                        bfReserved2;
                            DWORD                       bfOffBits;
} BITMAPFILEHEADER;

这个结构体实际的大小是2+4+2+2+4=14.而默认的时候,x86系统由于有内存对齐的操作,所以给他分配了4+4+(2+2)+4=16 bytes的大小,用sizeof(BITMAPFILEHEADER)=16可以证实这一点。但是问题就在这里,实际上bmp文件中,上述结构的大小为14bytes,如果按照下面的函数读取的话,就会发生错误。

fs.read((char*)&bitmapFileHeader,sizeof(BITMAPFILEHEADER))

就会发生错误,这是为什么呢?下面自习分析这个数据结构:

typedef struct tagBITMAPFILEHEADER {
                            WORD                        bfType;                       //2B,
                            DWORD                       bfSize;                      // 4B,这个是块头最大的数据类型,4B,所以按照这个对齐,
                            WORD                        bfReserved1;//2B
                            WORD                        bfReserved2;//2B
                            DWORD                       bfOffBits;//4B
} BITMAPFILEHEADER;

在内存中分配的块如下:

_________________________________________________________________
|................|\\\\\\\\\\\\\\\\\|......................|.........................|......................|.......................|
|...bfType..|\\padding\\|...bfSize..........|.bfReserved1....|.bfReserved2.|..bfOffBits.......|
|................|\\\\\\\\\\\\\\\\\|......................|..........................|......................|......................|
+------------------------------------------------------------------------------------------------------+
Bytes:1.2.....3.....4........5....6...7....8..........9...10..............11......12.....13.14.15.16..

我们可以看到,第3,4字节是为了内存对齐给添加进去的,我们依次把14B的文件头信息添加到这里的话,就会使数据发生错位。另外,如果读取16B的话,那么就会读取到不应该读取的内容。

影响到文件指针的定位,和后面的读取。这就是我把wingdi.h中的BITMAPFILEHEADER结构体直接copy到我文件中不能使用的原因。为了查明这个原因,我仔细的看了wingdi.h文件,发现它在这个结构体定义的前后有#include <pshpack2.h>#include <poppack.h>,查看这这些文件,才知道pshpack2.h实际上就是让结构体按照2B的大小对齐,而poppack则是上一个对于pack的设置失效。内部实际上是有一个栈在维护这个堆栈。

于是我把自己的结构体前后也加上了#include <pshpack2.h>和#include <poppack.h>,发现一切OK了。呵呵,大功告成。

后来我又发现,还有pshpack1.h,pshpack4.h,pshpack8.h等文件,如果include的话,就表示要按照1B,4B,8B的字节顺序对齐。(实际上是这里指定的值和结构体中块头最大的值,两者取最小值)。

比如

#include <pshpack4.h>

typedef struct _B
{
char a;
short b;
char c;
}B;

对于上述的机构体,其大小仍然是6.仍然是按照2B的大小对齐的。

另外查阅pshpack1.h等文件,发现对于结构体内存对齐的控制是通过如下的pragma指令来实现的。

所以,也可以直接在程序中用下面的指令,但是推荐使用include头文件的方法。

#pragma ack(push,n)//n=1,2,4,8

#pragma pack(pop)

#pragma pack(n)     

 

 

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

 

VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。

 

下面举例说明其用法。

 

#pragma pack(push) //保存对齐状态

 

#pragma pack(4)//设定为4字节对齐

 

struct test

 

{

 

char m1;

 

double m4;

 

int m3;

 

};

 

#pragma pack(pop)//恢复对齐状态

 

以上结构体的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1大小为1个字节。接着开始为m4分配空间,这时其偏移量为4,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于4),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(8),那么我们可以得到结构的大小为24。

 

另外,这样的操作是使得速度有所下降。所以不要滥用。

posted @ 2013-03-08 08:30  boys2012  阅读(499)  评论(0编辑  收藏  举报