Search code examples
c++filestructpadding

What is the best/most elegant way to write a struct contiguously to a file in C++?


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.


Solution

  • 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!