在从结构体图像数据转成Mat图像数据的过程中遇到这个问题:
摄像头是uvc协议,使用videocapture获取,在定义结构体时需要使用#pragma pack(1)
。这可以确保结构体的内存布局与外部数据格式一致,从而避免由于字节对齐导致的数据错误。
#pragma pack(1)
是一个预处理指令,用于指定结构体或联合体在内存中的对齐方式。在 C 和 C++ 中,编译器通常会按照默认对齐方式对结构体的成员进行排列,这可能导致结构体在内存中占用比实际需要的更多空间。使用 #pragma pack(1) 可以将结构体的对齐方式设置为 1 字节,从而减少内存占用。
何时 #pragma pack(1) 是必要的?
-
内存布局匹配:
如果你的结构体 MTImageData 是从外部数据源(如图像文件、网络协议等)读取的,并且这些数据源的格式要求使用特定的内存对齐方式(如没有填充字节),那么使用 #pragma pack(1) 是必要的。这可以确保结构体的内存布局与外部数据格式一致,从而避免由于字节对齐导致的数据错误。 -
节省内存:
如果你的数据结构很小,并且你希望最大程度地减少内存使用,那么使用 #pragma pack(1) 可以去除由于对齐而增加的填充字节。
#pragma pack()
这个指令结束了之前的对齐设置,恢复为默认的对齐方式。编译器会根据系统的默认设置对后续结构体进行对齐。
#pragma pack(1) // 结构体单字节对齐
typedef struct
{
MT_UINT16 width; // 图像宽度
MT_UINT16 height; // 图像高度
MT_UINT8 channel; // 图像通道数
MT_UINT8* data; // 图像数据
MT_UINT32 dlen; // 图像数据长度
} MTImageData, * PMTImageData;
#pragma pack()
示例讲解:
#include <iostream>
#include <cstdint>
struct DefaultAlign {
uint8_t a; // 1 字节
uint32_t b; // 4 字节
uint16_t c; // 2 字节
};
// 计算默认对齐情况下的结构体大小
int main() {
std::cout << "Size of DefaultAlign: " << sizeof(DefaultAlign) << std::endl; // 8 字节
return 0;
}
在这个示例中,DefaultAlign 结构体的大小为 8 字节,而不是 7 字节,因为编译器在 uint8_t a 和 uint32_t b 之间添加了 3 个填充字节。
有没有小伙伴会问,填充了三个字节不应该是10字节吗?
这实际上是编译器的优化机制在起作用。填充字节的数量不必多于使下一成员满足其对齐要求所需的最小数量。例如,只要填充 1 字节即可让 data按 4 字节对齐,所以编译器智能地只插入了 1 个字节的填充,而不增加多余的填充。编译器智能地识别这一需求,计算出插入 1 字节就可以满足对齐要求,而无需插入多余的填充字节(不需要 3 个字节),因为这会浪费空间。
下面解释一下:
- uint8_t a 占用 1 字节。
- 在 uint8_t a 和 uint32_t b 之间,编译器需要填充字节以确保 b 的地址是 4 字节对齐的。由于 a 只占用 1 字节,编译器在 a 后面添加了 3 个填充字节,使得 b 从地址的下一个 4 字节边界开始。
- uint32_t b 占用 4 字节。
- uint16_t c 占用 2 字节。c 直接跟在 b 后面,所以没有额外的填充字节。
由于整个结构体的大小需要是其最大成员大小的倍数,这里最大成员是 uint32_t,需要保证整体结构体的大小为 4 字节的倍数。
结构体大小计算:
a 的 1 字节 + 3 字节的填充 + b 的 4 字节 + c 的 2 字节 = 10 字节
但是由于结构体大小需要满足最大对齐要求(在这里是 4 字节),因此编译器会把整个结构体的大小对齐到下一个 4 字节的倍数,所以最终大小为 8 字节,而不是 10 字节。
再以最上面的代码举个例子:
typedef struct
{
MT_UINT16 width; // 图像宽度
MT_UINT16 height; // 图像高度
MT_UINT8 channel; // 图像通道数
MT_UINT8* data; // 图像数据
MT_UINT32 dlen; // 图像数据长度
} MTImageData, * PMTImageData;
- MT_UINT16 width 占用 2 字节。
- MT_UINT16 height 占用 2 字节,紧接着 width 之后,因此没有填充。
- MT_UINT8 channel 占用 1 字节,但由于它后面的 data 是一个指针(通常需要 4 字节对齐),所以编译器在 channel 后添加 1 个填充字节以满足对齐要求。
- MT_UINT8* data 占用 4 字节。它将从填充后的地址开始。
- MT_UINT32 dlen 占用 4 字节。