保持GCC不改变结构的大小(.BMP文件头)

时间:2014-12-02 18:22:59

标签: c gcc struct bmp

我目前正在尝试创建一个.BMP文件。根据我的定义,BMP头是54个字节长。代码编译和一切,但在尝试打开文件时,我收到“错误的标题格式”错误。

如果我执行sizeof(structtype),我得到56个字节而不是定义的54个 - 如果我使用值初始化结构,那么执行sizeof(newStruct),我得到8个字节。因为我需要将54个字节写入文件中,所以这很糟糕。

有没有办法让GCC不以这种方式改变结构大小?

以下是结构的定义:

typedef struct
{
  uint16_t typeSignature; // = "BM"
  uint32_t filesize;     //filesize in Bytes
  uint32_t reserved;     // = 0 for this program
  uint32_t headerOffset; // = 54
} BmpFileHeader;

typedef struct
{
  uint32_t infoHeaderSize;    //size of header in byte. ( = 40)
  uint32_t Width;             //width of file in pixels
  uint32_t Height;            // height of file in pixels

  uint16_t Colors;       //colorbits per pixel (24 for 3byte RGB)
  uint16_t bitsPerPixel;
  uint32_t Compression;       //compression mode; 0 for uncompressed.
  uint32_t SizeImg;         //if biCompress = 0, =0. Else: filesize.

  uint32_t xPelsPerMeter;     // for output device;
  uint32_t yPelsPerMeter;     // 0 for this program

  uint32_t ColorsUsed;      // Colours used; = 0 for all
  uint32_t ColorsImportant; // num. of used colours, 0 for all
} BmpInfoHeader;

typedef struct
{
  BmpFileHeader fileheader;
  BmpInfoHeader infoheader;
} bitmapHead; // sizeof = 56

这里是初始化新标题的函数:

bitmapHead* assembleHeader(int compCount)
{
  bitmapHead* newHeader = (bitmapHead*) calloc(1, 54);  
  newHeader->fileheader.typeSignature = 0x4D42;
  newHeader->fileheader.filesize = (compCount*100*51*3 + 54);
  newHeader->fileheader.reserved = 0;
  newHeader->fileheader.headerOffset = 54;


  newHeader->infoheader.infoHeaderSize = 40;
  newHeader->infoheader.Width = 100*compCount;
  newHeader->infoheader.Height = 51;
  newHeader->infoheader.Colors = 1; 
  newHeader->infoheader.bitsPerPixel = 21;
  newHeader->infoheader.Compression = 0;
  newHeader->infoheader.SizeImg = compCount*100*51*3;
  newHeader->infoheader.xPelsPerMeter = 0;
  newHeader->infoheader.yPelsPerMeter = 0;
  newHeader->infoheader.ColorsUsed = 0;
  newHeader->infoheader.ColorsImportant = 0;
  printf("%lu \n", sizeof(newHeader)); // This gives me 8. 
  return newHeader;
}

4 个答案:

答案 0 :(得分:6)

这是“不要那样做”的情况。具体来说,永远不要尝试布局与外部规范定义的字节模式匹配的C struct。 C标准对与此合作并不感兴趣,虽然某些编译器试图使其成为可能,但它比它的价值更麻烦。 (阅读Ada表示条款,如果您有兴趣了解认真解决这个问题的语言。)

您可以定义struct来表示内存中的BMP标头 ,但是您从磁盘读取/写入的内容应该是uint8_t的数组。雅莉。您必须编写函数以在内存中表示和磁盘表示之间显式转换。这些也是进行任何必要检查和处理字节序的好地方。

BmpFileHeader的工作示例:

// Fixed fields not represented in in-memory header.
typedef struct
{
    uint32_t filesize;     // total size of file in bytes
    uint32_t headerOffset; // offset from beginning of file to end of headers
                           // (normally 54)
} BmpFileHeader;

#define BMP_FILE_HEADER_MAGIC_1  0
#define BMP_FILE_HEADER_MAGIC_2  1
#define BMP_FILE_HEADER_FILESIZE 2
#define BMP_FILE_HEADER_RESERVED 6
#define BMP_FILE_HEADER_HDROFF   10
#define BMP_FILE_HEADER_SIZE     14
typedef uint8_t BmpFileHeaderOnDisk[BMP_FILE_HEADER_SIZE];

uint32_t
le32_to_cpu(uint8_t *p)
{
    return ((((uint32_t)p[0]) <<  0) | 
            (((uint32_t)p[1]) <<  8) |
            (((uint32_t)p[2]) << 16) |
            (((uint32_t)p[3]) << 24));
}

// Returns true if header is successfully parsed.
bool
load_bmp_file_header(BmpFileHeaderOnDisk ondisk, BmpFileHeader *inmem)
{
    if (ondisk[BMP_FILE_HEADER_MAGIC_1] != 'B' ||
        ondisk[BMP_FILE_HEADER_MAGIC_2] != 'M' ||
        le32_to_cpu(ondisk + BMP_FILE_HEADER_RESERVED) != 0)
        return false; // not a BMP file

    inmem->filesize = le32_to_cpu(ondisk + BMP_FILE_HEADER_FILESIZE);
    inmem->headerOffset = le32_to_cpu(ondisk + BMP_FILE_HEADER_HDROFF);
    return true;
}

以相反的方式,以及“信息”标题的处理,留作练习。

答案 1 :(得分:3)

可移植文件解析的基本思想是将文件视为字节流(类型signed charunsigned char),并且仅作为那样。您不应该尝试在这些字节上叠加struct或其他类型,因为这会使您的代码无法移植;毕竟,结构布局,字节顺序以及可能影响数据表示方式的许多其他因素都取决于平台。

一个好主意是定义这些功能;如果您担心它们不够有效,请在使用它们的翻译单元中将它们作为static inline函数提供。

/* portably read a 32 bit unsigned int in little-endian representation */
static uint32_t
read_le32(const unsigned char buf[4])
{
    uint32_t result;

    result  = (buf[0] & 0xff) <<  0;
    result |= (buf[1] & 0xff) <<  8;
    result |= (buf[2] & 0xff) << 16;
    result |= (buf[3] & 0xff) << 24;

    return (result);
}

并且类似于16位unsigned int:

static uint16_t
read_le16(const unsigned char buf[4])
{

    return (buf[0] & 0xff) | (buf[1] & 0xff) << 8;
}

现在你可以阅读这样的标题:

static void
read_bitmap_header(bitmapHead *bfh, const unsigned char buf[56])
{

    bfh->fileheader.typeSignature =  read_le16(buf +  0);
    bfh->fileheader.filesize =       read_le32(buf +  2);
    bfh->fileheader.reserved =       read_le32(buf +  6);
    bfh->fileheader.headerOffset =   read_le32(buf + 10);
    bfh->infoheader.infoHeaderSize = read_le32(buf + 14);
    bfh->infoheader.Width =          read_le32(buf + 18);
    /* ... */
}

一开始看起来真的很麻烦,但实际上这种方法非常清晰,并且可以实现高度可移植的代码。

答案 2 :(得分:1)

您希望将struct的属性类型指定为已打包。请参阅attributes上的海湾合作委员会文件。具体而言,您希望使用__packed__属性。 __packed__属性可最大限度地降低struct所需的内存要求。 使用__packed__ BmpFileHeader属性的示例就是这样,

typedef struct
{
  uint16_t typeSignature; // = "BM"
  uint32_t filesize;     //filesize in Bytes
  uint32_t reserved;     // = 0 for this program
  uint32_t headerOffset; // = 54
} __attribute__((__packed__))
BmpFileHeader;

您可以在其他struct数据结构中使用相同的方法。

答案 3 :(得分:0)

使用pragma pack;

#pragma pack(push, 1)
// your typedef structs
#pragma pack(pop)

实施例

#pragma pack(push,1)
typedef struct
{
  uint16_t typeSignature; // = "BM"
  uint32_t filesize;     //filesize in Bytes
  uint32_t reserved;     // = 0 for this program
  uint32_t headerOffset; // = 54
} BmpFileHeader;

typedef struct
{
  uint32_t infoHeaderSize;    //size of header in byte. ( = 40)
  uint32_t Width;             //width of file in pixels
  uint32_t Height;            // height of file in pixels

  uint16_t Colors;       //colorbits per pixel (24 for 3byte RGB)
  uint16_t bitsPerPixel;
  uint32_t Compression;       //compression mode; 0 for uncompressed.
  uint32_t SizeImg;         //if biCompress = 0, =0. Else: filesize.

  uint32_t xPelsPerMeter;     // for output device;
  uint32_t yPelsPerMeter;     // 0 for this program

  uint32_t ColorsUsed;      // Colours used; = 0 for all
  uint32_t ColorsImportant; // num. of used colours, 0 for all
} BmpInfoHeader;

typedef struct
{
  BmpFileHeader fileheader;
  BmpInfoHeader infoheader;
} bitmapHead; // now you will get sizeof = 54
#pragma pack(pop)

这是一个完整的问候世界;

演示代码

#include <stdio.h>
#include <stdint.h>

#pragma pack(push,1)
typedef struct
{
  uint16_t typeSignature; // = "BM"
  uint32_t filesize;     //filesize in Bytes
  uint32_t reserved;     // = 0 for this program
  uint32_t headerOffset; // = 54
} BmpFileHeader;

typedef struct
{
  uint32_t infoHeaderSize;    //size of header in byte. ( = 40)
  uint32_t Width;             //width of file in pixels
  uint32_t Height;            // height of file in pixels

  uint16_t Colors;       //colorbits per pixel (24 for 3byte RGB)
  uint16_t bitsPerPixel;
  uint32_t Compression;       //compression mode; 0 for uncompressed.
  uint32_t SizeImg;         //if biCompress = 0, =0. Else: filesize.

  uint32_t xPelsPerMeter;     // for output device;
  uint32_t yPelsPerMeter;     // 0 for this program

  uint32_t ColorsUsed;      // Colours used; = 0 for all
  uint32_t ColorsImportant; // num. of used colours, 0 for all
} BmpInfoHeader;

typedef struct
{
  BmpFileHeader fileheader;
  BmpInfoHeader infoheader;
} bitmapHead; // now you will get sizeof = 54
#pragma pack(pop)

int main()
{
    printf("Size of bitmapHead : %d\n", sizeof(bitmapHead));
}

演示输出

>pragmaExample
Size of bitmapHead : 54