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

sxpgvts3  于 2023-07-01  发布在  其他
关注(0)|答案(3)|浏览(123)

我正在尝试将图像数据写入BMP文件。我发现了这个website,它为文件头提供了以下结构:

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;

我知道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;                            // 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];
    };

看看我是否可以将数组写入文件,但我遇到了同样的问题。

o8x7eapl

o8x7eapl1#

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(以及更高版本)不使用此设置。

2lpgd968

2lpgd9682#

作为Yksisarvinen和**n。我会在Reddit上看到你们,在上面的评论中提到,我没有包括pragma pack指令。
以下是修复它的更改:

#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)

谢谢大家!

aiazj4mn

aiazj4mn3#

恕我直言,最优雅的方法是将方法复制到缓冲区中。这允许您控制间距(如果有)和字节序:

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);
    //...
}

然后你可以使用它来写块:

uint8_t  buffer[54];
BMPHeader header;
//...
char * p_buffer = &buffer[0];
header.write_to_buffer(p_buffer);
destination_file.write(buffer, sizeof(buffer));

类似地,编写一个方法从缓冲区读取成员。read对于非POD成员(如字符串)非常有效。
附带的方法size_in_buffer()将返回每个成员在缓冲区中占用的累积大小。这在分配动态数组(缓冲区)来保存数据时非常有用。
效率是基于写内存比写文件更快的理性。此外,对文件的块写入比对每个成员单独进行许多小写入更快、更高效。流在保持运行时效率最高。
另外,在互联网上搜索“C++序列化”。

  • 注意:此版本不需要任何编译器指令,并且应该在支持文件的所有平台上工作。

相关问题