最近上一门名为《数字图像处理》的课程,今天打算做一个小实验:将一张BMP图像的每一个像素都置换成相补的颜色值(255-当前像素值)。题目很简单,为阅读相关网页,了解了一下BMP图像头的相关信息,定义了如下结构体:
typedef struct BITMAPFILEHEADER
{
short int bfType; //位图文件的类型,必须为BM
long bfSize; //文件大小,以字节为单位
short int bfReserverd1; //位图文件保留字,必须为0
short int bfReserverd2; //位图文件保留字,必须为0
long bfbfOffBits; //位图文件头到数据的偏移量,以字节为单位
}BITMAPFILEHEADER;
typedef struct BITMAPINFOHEADER
{
long biSize; //该结构大小,字节为单位
long biWidth; //图形宽度以象素为单位
long biHeight; //图形高度以象素为单位
short int biPlanes; //目标设备的级别,必须为1
short int biBitcount; //颜色深度,每个象素所需要的位数
long biCompression; //位图的压缩类型
long biSizeImage; //位图的大小,以字节为单位
long biXPelsPermeter; //位图水平分辨率,每米像素数
long biYPelsPermeter; //位图垂直分辨率,每米像素数
long biClrUsed; //位图实际使用的颜色表中的颜色数
long biClrImportant; //位图显示过程中重要的颜色数
}BITMAPINFOHEADER;
typedef struct BITMAP
{
BITMAPFILEHEADER file; //文件信息区
BITMAPINFOHEADER info; //图象信息区
}BITMAP;
(注:结构体的定义参考了http://blog.youkuaiyun.com/courage89/article/details/7187255和http://blog.youkuaiyun.com/zhaozidong86/article/details/6628469)
BMP的图像头大小为54字节(十六进制36H)。为了防止出错(其实是第一次的测试结果有点不正常),我仔细查看了一下结构体定义的准确性。
结果出人意料:
可以自习数一下上面结构体的定义,BITMAPFILEHEADER的字节数应该是0DH,而不应该是10H。
为什么会出现这种情况呢?
我的猜想是结构体内部存在对齐。换句话说,编译器为了物理结构上处理的方便,在结构体内部空余了部分位置。由于结构体中只有2字节和4字节数据,最可能的就是四字节对齐。
为此,我将BITMAPFILEHEADER稍作改变:
结果是:
可以看见,我在2(+2)+4+2+2的本已对齐的基础上多添加一个大小为2字节的字段,结构体的总大小却增加了4字节。这个结果证实了我的想法。
为了进一步证实我的想法, 我又对这个结构体做了如下修改:
现在结构体的字段变成了2+2+4+2+2+4的已对齐的格式,我的猜想是其字节大小为10H。结果如下:
实验的结果跟预测结果是吻合的。
那么,结论是,C语言结构体在内部确实存在对齐规则。
上述实验是在Codeblocks 12.11环境下进行的,编译器是CB默认的GNU-GCC。那么在WIndows的VC环境下,是不是有同样的结果呢?
以下分别是上述情况的运行结果:
可见,VC的结构体内部也是存在对齐规则的。
结构体内部对齐的好处是编译器更为方便的内存管理,但这种编译器的自动优化,却给程序设计者带来很多问题。带给我最直接的问题是,我不能通过
这种简单的方式读取图像的文件头和信息头数据。
那么该如何改进呢?当然对结构体的每个字段都单独读取是可行的,这样会添加不少几乎重复的代码,却是一个百试百灵的绝招。另一个针对本次实验特定问题的处理方法是,首先对没有对齐的数据单独读取内容(本实验中,只有BITMAPFILEHEADER),对剩下的内容小心设置每次读取数据的数量,即可险中求胜。