英文:
What is the best/most elegant way to write a struct contiguously to a file in C++?
问题
我试图将图像数据写入BMP文件。我找到了这个网站,该网站提供了以下文件头的结构体:
typedef struct { // 总共:54字节
uint16_t type; // 魔术标识符:0x4d42
uint32_t size; // 文件大小(字节)
uint16_t reserved1; // 未使用
uint16_t reserved2; // 未使用
uint32_t offset; // 图像数据的偏移量(字节),从文件开头开始(54字节)
uint32_t dib_header_size; // DIB头部大小(字节)(40字节)
int32_t width_px; // 图像宽度
int32_t height_px; // 图像高度
uint16_t num_planes; // 颜色平面数
uint16_t bits_per_pixel; // 每像素位数
uint32_t compression; // 压缩类型
uint32_t image_size_bytes; // 图像大小(字节)
int32_t x_resolution_ppm; // 每米像素数
int32_t y_resolution_ppm; // 每米像素数
uint32_t num_colors; // 颜色数量
uint32_t important_colors; // 重要颜色
} BMPHeader;
我知道C/C++不能保证结构体中的数据会按顺序存储,因此像这样简单地将头部结构体写入文件是行不通的:
BMPHeader header(width, height, bytespp, fileSize);
int num_read = fwrite(&header, BMP_HEADER_SIZE, 1, fp);
或者:
BMPHeader* header = new BMPHeader(width, height, bytespp, fileSize);
int num_read = fwrite(header, BMP_HEADER_SIZE, 1, fp);
使用以下构造函数(这可能是不必要的信息,但我还是包括了它,以防它可能有用):
BMPHeader(
unsigned int width, unsigned int height, unsigned short bytespp, unsigned int fileSize) :
type(MAGIC_VALUE), size(fileSize),
reserved1(RESERVED), reserved2(RESERVED), offset(BMP_HEADER_SIZE),
dib_header_size(40), width_px(width), height_px(height), num_planes(NUM_PLANE),
bits_per_pixel(bytespp*BITS_PER_BYTE), compression(COMPRESSION),
image_size_bytes(width * height * bytespp), x_resolution_ppm(0x00),
y_resolution_ppm(0x00), num_colors(0x00), important_colors(IMPORTANT_COLORS)
{
}
这样做不会奏效,因为无法保证变量按照期望的顺序存储,也不能保证变量之间没有填充。
不过,我还是尝试了这种方式,但并不奇怪,它没有奏效。
我的问题是:是否有一种更加优雅的方式将数据写入文件,而不必逐个写入每个成员变量并确保顺序正确?
我尝试了将结构体实例化到堆上作为BMPHeader*
,也尝试了在堆栈上实例化它。
我还尝试使用联合,如下所示:
struct BMPHeader
{
union
{
struct
{
unsigned short type; // 魔术标识符:0x4d42
unsigned int size; // 文件大小(字节)
unsigned short reserved1; // 未使用
unsigned short reserved2; // 未使用
unsigned int offset; // 图像数据的偏移量(字节),从文件开头开始(54字节)
unsigned int dib_header_size; // DIB头部大小(字节)(40字节)
unsigned int width_px; // 图像宽度
unsigned int height_px; // 图像高度
unsigned short num_planes; // 颜色平面数
unsigned short bits_per_pixel; // 每像素位数
unsigned int compression; // 压缩类型
unsigned int image_size_bytes; // 图像大小(字节)
unsigned int x_resolution_ppm; // 每米像素数
unsigned int y_resolution_ppm; // 每米像素数
unsigned int num_colors; // 颜色数量
unsigned int important_colors; // 重要颜色
};
unsigned char raw[54];
};
}
以尝试将数组写入文件,但遇到了相同的问题。
英文:
I'm trying to write image data to a BMP file. I found this website which proposes the following struct for the file header:
typedef struct { // Total: 54 bytes
uint16_t type; // Magic identifier: 0x4d42
uint32_t size; // File size in bytes
uint16_t reserved1; // Not used
uint16_t reserved2; // Not used
uint32_t offset; // Offset to image data in bytes from beginning of file (54 bytes)
uint32_t dib_header_size; // DIB Header size in bytes (40 bytes)
int32_t width_px; // Width of the image
int32_t height_px; // Height of image
uint16_t num_planes; // Number of color planes
uint16_t bits_per_pixel; // Bits per pixel
uint32_t compression; // Compression type
uint32_t image_size_bytes; // Image size in bytes
int32_t x_resolution_ppm; // Pixels per meter
int32_t y_resolution_ppm; // Pixels per meter
uint32_t num_colors; // Number of colors
uint32_t important_colors; // Important colors
} BMPHeader;
I know that C/C++ does not guarantee that the data from a struct will be stored contiguously, So simply writing the header struct to the file like so:
BMPHeader header(width, height, bytespp, fileSize);
int num_read = fwrite(&header, BMP_HEADER_SIZE, 1, fp);
or so:
BMPHeader* header = new BMPHeader(width, height, bytespp, fileSize);
int num_read = fwrite(header, BMP_HEADER_SIZE, 1, fp);
using the following constructor (this is probably unnecessary information, but including it incase it might be useful):
BMPHeader(
unsigned int width, unsigned int height, unsigned short bytespp, unsigned int fileSize) :
type(MAGIC_VALUE), size(fileSize),
reserved1(RESERVED), reserved2(RESERVED), offset(BMP_HEADER_SIZE),
dib_header_size(40), width_px(width), height_px(height), num_planes(NUM_PLANE),
bits_per_pixel(bytespp*BITS_PER_BYTE), compression(COMPRESSION),
image_size_bytes(width * height * bytespp), x_resolution_ppm(0x00),
y_resolution_ppm(0x00), num_colors(0x00), important_colors(IMPORTANT_COLORS)
{
}
won't work since there is no guarantee that the variables will be stored in the order that they are written, nor is there a guarantee that there won't be padding between the variables.
Regardless, I tried it anyways and, unsurprisingly, it didn't work.
My question is this: Is there any way to write the data to a file in a more elegant way than having to individually write each member variable to the file in the order that I want?
I tried instantiating the struct both onto the heap as a BMPHeader*
, as well as on the stack.
I also tried using a union like so:
struct BMPHeader
{
union
{
struct
{
unsigned short type; // Magic identifier: 0x4d42
unsigned int size; // File size in bytes
unsigned short reserved1; // Not used
unsigned short reserved2; // Not used
unsigned int offset; // Offset to image data in bytes from beginning of file (54 bytes)
unsigned int dib_header_size; // DIB Header size in bytes (40 bytes)
unsigned int width_px; // Width of the image
unsigned int height_px; // Height of image
unsigned short num_planes; // Number of color planes
unsigned short bits_per_pixel; // Bits per pixel
unsigned int compression; // Compression type
unsigned int image_size_bytes; // Image size in bytes
unsigned int x_resolution_ppm; // Pixels per meter
unsigned int y_resolution_ppm; // Pixels per meter
unsigned int num_colors; // Number of colors
unsigned int important_colors; // Important colors
};
unsigned char raw[54];
};
to see if I could write the array to the file, but I ran into the same issue.
答案1
得分: 0
Microsoft多年来一直在使用这种结构,对于在Visual Studio中的结构类型的标准设置,对文件的单次写入有效。
但是有一个讨厌的陷阱:
#pragma pack(push,2)
typedef struct tagBITMAPFILEHEADER
{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
#pragma pack(pop)
必须强制执行以确保字段 bfType
和 bfReserved1/2
存储在两个字节上。
相比之下,BITMAPINFOHEADER
(以及后来的变种)不使用这个设置。
英文:
Microsoft kept using this structure for years and a single write to a file works with the standard settings of struct types in Visual Studio.
But there is a nasty trap:
#pragma pack(push,2)
typedef struct tagBITMAPFILEHEADER
{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
#pragma pack(pop)
must be enforced to guarantee that the fields bfType
and bfReserved1/2
are stored on two bytes.
By contrast, the BITMAPINFOHEADER
(and later variants) does not use this setting.
答案2
得分: 0
以下是翻译好的部分:
"如上面的评论中提到的Yksisarvinen和n. m. will see y'all on Reddit,我没有包括pragma pack
指令。
这是修复的更改:
#pragma pack(push, 1)
struct BMPHeader
{
uint16_t type; // 魔术标识符:0x4d42
uint32_t size; // 文件大小(以字节为单位)
uint16_t reserved1; // 未使用
uint16_t reserved2; // 未使用
uint32_t offset; // 从文件开头开始的图像数据偏移量(54字节)
uint32_t dib_header_size; // DIB标头大小(以字节为单位,40字节)
uint32_t width_px; // 图像宽度
uint32_t height_px; // 图像高度
uint16_t num_planes; // 颜色平面数
uint16_t bits_per_pixel; // 每像素位数
uint32_t compression; // 压缩类型
uint32_t image_size_bytes; // 图像大小(以字节为单位)
uint32_t x_resolution_ppm; // 每米像素数
uint32_t y_resolution_ppm; // 每米像素数
uint32_t num_colors; // 颜色数量
uint32_t important_colors; // 重要颜色数
BMPHeader(
unsigned int width, unsigned int height, unsigned short bytespp, unsigned int fileSize) :
type(MAGIC_VALUE), size(fileSize),
reserved1(RESERVED), reserved2(RESERVED), offset(BMP_HEADER_SIZE),
dib_header_size(40), width_px(width), height_px(height), num_planes(NUM_PLANE),
bits_per_pixel(bytespp*BITS_PER_BYTE), compression(COMPRESSION),
image_size_bytes(width * height * bytespp), x_resolution_ppm(0x00),
y_resolution_ppm(0x00), num_colors(0x00), important_colors(IMPORTANT_COLORS)
{
}
};
#pragma pack(pop)
谢谢大家!
英文:
As Yksisarvinen and n. m. will see y'all on Reddit mentioned in the comments above, I didn't include the pragma pack
directive.
Here is the change that fixed it:
#pragma pack(push, 1)
struct BMPHeader
{
uint16_t type; // Magic identifier: 0x4d42
uint32_t size; // File size in bytes
uint16_t reserved1; // Not used
uint16_t reserved2; // Not used
uint32_t offset; // Offset to image data in bytes from beginning of file (54 bytes)
uint32_t dib_header_size; // DIB Header size in bytes (40 bytes)
uint32_t width_px; // Width of the image
uint32_t height_px; // Height of image
uint16_t num_planes; // Number of color planes
uint16_t bits_per_pixel; // Bits per pixel
uint32_t compression; // Compression type
uint32_t image_size_bytes; // Image size in bytes
uint32_t x_resolution_ppm; // Pixels per meter
uint32_t y_resolution_ppm; // Pixels per meter
uint32_t num_colors; // Number of colors
uint32_t important_colors; // Important colors
BMPHeader(
unsigned int width, unsigned int height, unsigned short bytespp, unsigned int fileSize) :
type(MAGIC_VALUE), size(fileSize),
reserved1(RESERVED), reserved2(RESERVED), offset(BMP_HEADER_SIZE),
dib_header_size(40), width_px(width), height_px(height), num_planes(NUM_PLANE),
bits_per_pixel(bytespp*BITS_PER_BYTE), compression(COMPRESSION),
image_size_bytes(width * height * bytespp), x_resolution_ppm(0x00),
y_resolution_ppm(0x00), num_colors(0x00), important_colors(IMPORTANT_COLORS)
{
}
};
#pragma pack(pop)
Thanks all!
答案3
得分: 0
IMHO,最优雅的方法是将方法复制到缓冲区中。这允许您控制间距(如果有的话),还可以控制字节序:
struct BMPHeader
{ // 总共: 54字节
uint16_t type; // 魔术标识符: 0x4d42
uint32_t size; // 文件大小(字节)
uint16_t reserved1; // 未使用
uint16_t reserved2; // 未使用
uint32_t offset; // 从文件开头偏移的图像数据(字节,54字节)
uint32_t dib_header_size; // DIB标头大小(字节,40字节)
// ...
void write_to_buffer(uint8_t *& p_buffer) const;
};
void BMPHeader::write_to_buffer(uint8_t *& p_buffer) const
{
*p_buffer = (uint8_t *) &type;
p_buffer += sizeof(type);
*p_buffer = (uint8_t *) &size;
p_buffer += sizeof(size);
// ...
}
然后,您可以使用块写入它:
uint8_t buffer[54];
BMPHeader header;
// ...
char * p_buffer = &buffer[0];
header.write_to_buffer(p_buffer);
destination_file.write(buffer, sizeof(buffer));
类似地,编写一个方法来从缓冲区中读取成员。对于非POD成员(如字符串),读取效果非常好。
伴随的方法 size_in_buffer()
将返回每个成员在缓冲区中占用的累积大小。这在分配用于保存数据的动态数组(缓冲区)时非常有用。
效率基于写入内存比写入文件更快的理念。此外,对文件进行块写入比为每个成员单独进行多次小写入更快且更有效。流在保持运行时效率最高。
此外,搜索互联网上的 "C++ 序列化"。
注意:此版本不需要任何编译器指令,并且应该在支持文件的所有平台上运行。
英文:
IMHO, the most elegant method is to copy the methods into a buffer. This allows you to controls the spacing (if any) and also Endianness:
struct BMPHeader
{ // Total: 54 bytes
uint16_t type; // Magic identifier: 0x4d42
uint32_t size; // File size in bytes
uint16_t reserved1; // Not used
uint16_t reserved2; // Not used
uint32_t offset; // Offset to image data in bytes from beginning of file (54 bytes)
uint32_t dib_header_size; // DIB Header size in bytes (40 bytes)
//...
void write_to_buffer(uint8_t *& p_buffer) const;
};
void BMPHeader::write_to_buffer(uint8_t *& p_buffer) const
{
*p_buffer = (uint8_t *) &type;
p_buffer += sizeof(type);
*p_buffer = (uint8_t *) &size;
p_buffer += sizeof(size);
//...
}
You can then use it with block writing:
uint8_t buffer[54];
BMPHeader header;
//...
char * p_buffer = &buffer[0];
header.write_to_buffer(p_buffer);
destination_file.write(buffer, sizeof(buffer));
Similarly, write a method to read the members from a buffer. The read works really nice for non-POD members, such as strings.
An accompanying method size_in_buffer()
would return the accumulative size that each member would occupy in the buffer. This is great when allocating a dynamic array (buffer) to hold the data.
The efficiency is based on the rational that writing to memory is faster than writing to a file. Also, block writing to a file is faster and more efficient than many small writes for each member separately. A stream is most efficient when it is kept running.
Also, search the internet for "C++ serialization".
Note: This version does not require any compiler directives and should work on all platforms that support files.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论