直接用struct读BMP文件头会出错,因为编译器默认自然对齐导致结构体内存布局与BMP文件的1字节对齐二进制布局不一致,造成字段偏移错误;应使用#pragma pack(push, 1)强制1字节对齐,并严格按规范用uint16_t/uint32_t定义字段,读取时先校验bfType和biSize,以bfOffBits为准定位像素数据。

为什么直接用 struct 读 BMP 文件头会出错
因为 BMP 文件头是按字节对齐(1-byte alignment)写入磁盘的,而 C++ 编译器默认会对 struct 成员做自然对齐(比如 int 对齐到 4 字节边界),导致结构体大小变大、字段偏移错位。你用 fread(&header, sizeof(header), 1, fp) 读出来,bfOffBits 可能根本不是文件里那个值——它被编译器“撑开”了。
- 典型现象:
bfSize读成 0 或极大随机数,biWidth和biHeight值明显错乱 - 根本原因:结构体实际内存布局 ≠ BMP 规范定义的二进制布局
- 解决方向只有两个:强制编译器按 1 字节对齐,或手动解析字节流(不推荐重复造轮子)
用 #pragma pack(1) 安全定义 BMP 头结构体
这是最常用也最稳妥的做法,告诉编译器这个结构体所有成员紧挨着排,不插填充字节。注意必须在 struct 定义前后加保护,避免影响后续其他结构体。
#pragma pack(push, 1)
struct BITMAPFILEHEADER {
uint16_t bfType; // "BM"
uint32_t bfSize; // 整个文件大小
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits; // 像素数据起始偏移
};
struct BITMAPINFOHEADER {
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
#pragma pack(pop)
-
#pragma pack(push, 1)比#pragma pack(1)更安全,能还原之前对齐设置 - 字段类型必须严格匹配 BMP 规范:用
uint16_t/uint32_t,别用unsigned short或long(跨平台大小不确定) - Windows SDK 的
BITMAPFILEHEADER本身也是#pragma pack(2),但 BMP 文件实际是 1 字节对齐,所以必须用 1
读取时务必检查 bfType 和 biSize 再继续
BMP 有多种子格式(如 OS/2 BMP、BITMAPV5HEADER),biSize 字段决定了后面结构体实际长度。如果只硬编码读 sizeof(BITMAPINFOHEADER),遇到 biSize == 124(即 BITMAPV5HEADER)就会把后续数据全读歪。
- 先读
BITMAPFILEHEADER,验证bfType == 0x4D42(即 'B'+'M') - 再读前 4 字节获取
biSize,判断是 40(标准 BITMAPINFOHEADER)、108 还是 124 - 只支持
biSize == 40的场景下,也要加断言:assert(biSize == 40),否则静默错误更难排查 - 注意:BMP 是小端序,x86/x64 下无需额外转换,但嵌入式平台需留意
容易被忽略的细节:文件头之后可能有调色板,bfOffBits 才是真正起点
很多教程直接从 sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) 开始读像素,这仅适用于 24/32 位真彩色图。如果是 1/4/8 位索引色图,中间一定存在调色板(RGBQUAD 数组),长度由 biClrUsed 或 1 决定,而 bfOffBits 就是指向调色板之后、像素数据开头的绝对偏移。
- 永远以
bfOffBits为准 seek 到像素起始位置,不要自己算 - 调色板每个
RGBQUAD是 4 字节,但它的结构体同样要#pragma pack(1) - 行字节数必须 4 字节对齐(
padding = (4 - ((width * bits_per_pixel + 7) / 8) % 4) % 4),读每行时得跳过末尾 padding
对齐不是玄学,是二进制 I/O 的基本契约。一旦结构体定义和文件布局不一致,错一个字节,后面全崩——尤其当你要处理非标准 BMP(比如某些相机导出带自定义块的)时,bfOffBits 和 biSize 就是你唯一可信的路标。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











