前几天为了做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
于是我把自己的结构体前后也加上了#include
后来我又发现,还有pshpack1.h,pshpack4.h,pshpack8.h等文件,如果include的话,就表示要按照1B,4B,8B的字节顺序对齐。(实际上是这里指定的值和结构体中块头最大的值,两者取最小值)。
比如
#include
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)
另外,这样的操作是使得速度有所下降。所以不要滥用。