什么是在C++中将结构以最佳/最优雅的方式连续写入文件的方法?

huangapple go评论85阅读模式
英文:

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)

必须强制执行以确保字段 bfTypebfReserved1/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

以下是翻译好的部分:

"如上面的评论中提到的Yksisarvinenn. 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.

huangapple
  • 本文由 发表于 2023年6月25日 21:51:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76550737.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定