内存越界的灾难
Tag C语言, 内存, 越界, on by view 3437

今天为了修复一个bug折腾了将近一天的时间,原本是计划在沙箱子项目里面添加命令行指定配置文件路径功能,结果被我发现之前留下的一个坑,但是整个发现过程花了一整下午的时间。

调试过程中发现明明传入到函数中的路径printf是正确的,可是当执行到fopen时却直接异常退出,于是gdb跟踪调试

filename_interrupt.png

调试显示传入的文件名是正常的,但是当执行到#93步时文件名很明显被截断了,这是一件非常奇怪的事情,我甚至怀疑fopen函数是不是对传入文件名长度有限制,可事实上不是这样的。因为我后来专门写了个测试代码,同样的文件名路径,fopen却是正常的。我又怀疑是不是因为文件名字符串是malloc分配的原因,测试后却也是正常的。

非常纠结,之后决定采用大段大段的砍掉无关代码的方法来更清晰的定位错误可能的位置,因为代码带多确实看着眼花缭乱。当我砍到只剩下调用config_read的时候,惊奇的发现上面内存初始化是这样的:

// alloc memory for path string    
executable = (char*)malloc(sizeof(EXE_LEN));    
memset(executable, 0, sizeof(EXE_LEN));

shit! 这儿的EXE_LEN是int型指定文件名长度的啊,也就是说sizeof(EXE_LEN)是4byte,坑爹啊。但是我后面给这个内存strncpy了73个字符组成的字符串,嗯,编译器没给我报错,然后这内存几经传递传到了fopen里面,却被莫名的修改了,呵呵,修改了活该啊,谁让我把剩下的69个字符强行放入了它不该放的地方呢。其实应该是这个字符串越界到了fopen函数所使用的内存去,被修改也是太正常不过了。

C语言编程确实需要小心谨慎,因为编译器给你的提示实在是太少,逻辑错了就会按照错的运行,结果便会是谬之千里。



bmp文件格式分析
Tag bmp, C语言, 二进制, 文件读取, 格式, on by view 6019

bmp格式图片文件由4部分组成:文件头(BitmapFileHeader),信息头(BitmapInfoHeader),颜色表(RGBQuad),像素矩阵(RGB)[即数据区]。

文件头(BitmapFileHeader)

typedef struct _BITMAPFILEHEADER
{
	BYTE2  bfType;  //类型标识0x4d42
	BYTE4  bfSize;  //文件大小(字节)
	BYTE2  bfReserved1;  //保留1(0)
	BYTE2  bfReserved2;  //保留2(0)
	BYTE4  bfOffBits;  //数据区偏移(字节)
} BitmapFileHeader;

信息头(BitmapInfoHeader)

typedef struct _BITMAPINFOHEADER{ 
	BYTE4  biSize;  //本结构的大小,根据不同的操作系统而不同,在Windows中,此字段的值总为0x28字节=40字节
	BYTE4  biWidth;  //图片宽度(px)
	BYTE4  biHeight;  //图片高度(px)
	BYTE2  biPlanes;  //目标设备的级别(1)
	BYTE2  biBitCount;  //每个像素所需的位数,必须是1(双色),4(16色),8(256色)或24(真彩色)之一
	BYTE4  biCompression;  //是否压缩(0不压缩)
	BYTE4  biSizeImage;  //图像数据区大小
	BYTE4  biXPelsPerMeter;  //水平分辨率,像素每米
	BYTE4  biYPelsPerMeter;  //垂直分辨率,像素每米
	BYTE4  biClrUsed;  //BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为0x100=256
	BYTE4  biClrImportant;  //重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,
                                //当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
} BitmapInfoHeader;

颜色表(RGBQuad)

typedef struct _RGBQUAD {
	BYTE1  rgbBlue;  //蓝色分量
	BYTE1  rgbGreen;  //绿色分量
	BYTE1  rgbRed;  //红色分量
	BYTE1  rgbReserved;  //保留(0),非0则为alpha通道
} RGBQuad;

像素

typedef struct _RGBPx
{
	BYTE1  R;  //红色分量
	BYTE1  G;  //绿色分量
	BYTE1  B;  //蓝色分量
} RGBPx;

image_matrix.png

当图像颜色为24位时,即为全彩色RGB模式,无颜色表,所有颜色皆为重要色,因此biBitCount=24时文件中没有RGBQuad区域;若biBitCount为其他值,则图像为索引模式,颜色表中包含图像所用到的颜色,RGBQuad区域为RGBQuad结构体数组。文件中文件头(BitmapFileHeader),信息头(BitmapInfoHeader),颜色表(RGBQuad)三个区域之后是像素矩阵区域。

像素矩阵区中对于24位全色彩文件是RGBQuad区域为RGBQuad结构体数组,对于索引模式图像会将颜色表按照先后顺序编号,例如4位颜色,16色,将会编号0~15号颜色,如此0000b(1)表示第1种颜色,0001b(2),0010b(3),0011b(4),0100b(5)……故一字节可以表示两个像素,如0xa2=10100010b即为11号和2号色。

关于像素矩阵中还有一个问题需要注意,那就是“4字节边界”。像素矩阵的存储是遵循4字节边界存储的,也就是说每一行像素的数据字节大小必须是4的倍数,可是实际上有些图片存储后并不能保证每行的字节数是4的倍数;如一幅1366*768的24位图像,这种图像一像素24位即3字节,则每行1366*3=4098字节,而4098%4=2,余2并不是4的倍数,此时应当在最后2字节后面补充2字节(0x0)。故,在读取时应当注意每行像素后面是否有空边界。

最后,读取的像素还要进行倒置才能够正确的显示,因为原像素矩阵是上下颠倒,左右颠倒的。

paint_picture_reverse_demo.png

最后附上我自己实现的零依赖的bmp解析项目,直接读取二进制bmp文件解析,并带有基础图像变换函数https://github.com/organc/image 。