读取BMP文件头数值与微软官方定义不符,求技术排查
问题概述
读取BMP文件的BITMAPFILEHEADER和BITMAPINFOHEADER时,得到的数值与官方定义完全不符:例如bfType应为0x4D42(ASCII的"BM"),实际读取到55551,其他字段数值也全部错乱。错误输出如下:
bfType: 55551
bfSize: 1179258880
bfReserved1: 17993
bfReversed2: 256
bfOffBits: 1207959809
biSize: 18432
biWidth: 1477239551
biHeight: 1598243657
biPlanes: 21072
biBitCount: 17999
biCompression: 4541513
biSizeImage: 257
biXPelsPerMeter: 1766606860
biYPelsPerMeter: 268595054
biClrUsed: 1852637184
biClrImportant: 1196585588
错误原因
1. 结构体字节对齐导致的填充字节
微软官方定义的BMP文件头是紧凑1字节对齐的:
BITMAPFILEHEADER固定为14字节BITMAPINFOHEADER固定为40字节
但C语言编译器默认会按照结构体成员的最大对齐单位(例如DWORD是4字节)自动插入填充字节,导致自定义结构体的实际大小大于官方定义。比如BITMAPFILEHEADER中,bfType是2字节的WORD,后续bfSize是4字节的DWORD,编译器会在bfType后添加2字节填充以满足对齐要求,最终结构体大小变成16字节而非14字节。读取时会多读填充字节,导致整个数据错位,所有字段数值完全错误。
2. 未验证文件有效性
代码中未在读取后验证bfType是否为0x4D42,无法及时发现读取错误。
解决方案
1. 强制结构体1字节对齐
根据使用的编译器,给结构体添加对齐指令:
针对GCC/Clang编译器
修改bmp.h中的结构体定义,添加__attribute__((packed)):
#include <stdint.h> typedef uint8_t BYTE; typedef uint32_t DWORD; typedef int32_t LONG; typedef uint16_t WORD; typedef struct __attribute__((packed)) { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; typedef struct __attribute__((packed)) { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; typedef struct { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } RGBTRIPLE;
针对MSVC编译器
使用#pragma pack指令强制1字节对齐:
#include <stdint.h> typedef uint8_t BYTE; typedef uint32_t DWORD; typedef int32_t LONG; typedef uint16_t WORD; #pragma pack(push, 1) typedef struct { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; typedef struct { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; #pragma pack(pop) typedef struct { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } RGBTRIPLE;
2. 修正读取逻辑并添加有效性验证
修改main.c中的读取代码,确保读取正确并验证BMP文件标识:
// Public Libraries #include <stdio.h> #include <stdlib.h> // Private Libraries #include "bmp.h" // Start int main(int argc, char* argv[]) { if (argc != 4) { printf("ERROR: Command Line\n"); printf("Usage: EXE In Out Filter\n\n"); return 1; } // Open Original Image FILE* Original = fopen(argv[1], "rb"); if (Original == NULL) { printf("ERROR: Original Image Can't Be Found\n"); printf("Check File Name\n\n"); return 1; } printf("Image Found!\n\n"); // Read Bitmap File Header BITMAPFILEHEADER BF; if (fread(&BF, sizeof(BITMAPFILEHEADER), 1, Original) != 1 || BF.bfType != 0x4D42) { printf("ERROR: Not a valid BMP file\n"); fclose(Original); return 1; } printf("Bitmap File Header Read Successfully\n"); printf("bfType: 0x%X (%d)\n", BF.bfType, BF.bfType); printf("bfSize: %u\n", BF.bfSize); printf("bfOffBits: %u\n\n", BF.bfOffBits); // Read Bitmap Info Header BITMAPINFOHEADER BI; if (fread(&BI, sizeof(BITMAPINFOHEADER), 1, Original) != 1) { printf("ERROR: Failed to read Info Header\n"); fclose(Original); return 1; } printf("Bitmap Info Header Read Successfully\n"); printf("biSize: %u\n", BI.biSize); printf("biWidth: %d\n", BI.biWidth); printf("biHeight: %d\n", BI.biHeight); printf("biBitCount: %u\n", BI.biBitCount); fclose(Original); return 0; }
验证
编译修改后的代码,运行后应该能读取到符合官方定义的数值:例如bfType会显示0x4D42(19778),其他字段数值也会与BMP文件实际信息匹配。
内容的提问来源于stack exchange,提问作者Kervan96

