【问题标题】:Create BMP header in C (can't limit 2 byte fields)在 C 中创建 BMP 标头(不能限制 2 字节字段)
【发布时间】:2015-06-21 16:54:33
【问题描述】:

我这样做是基于:

https://en.wikipedia.org/wiki/BMP_file_format

我想用 C 语言从头开始创建 BMP 图像。

#include <stdio.h>
#include <stdlib.h>

typedef struct HEADER {
    short FileType;
    int FileSize;
    short R1;
    short R2;
    int dOffset;
} tp_header;

int main () {
    FILE *image;

    image = fopen("test.bmp", "w");

    tp_header bHeader;

    bHeader.FileType = 0x4D42;
    bHeader.FileSize = 70;
    bHeader.R1 = 0;
    bHeader.R2 = 0;
    bHeader.dOffset = 54;

    fwrite(&bHeader, sizeof(struct HEADER), 1, image);

    return 0;
}

我应该得到输出文件:

42 4D 46 00 00 00 00 00 00 00 36 00 00 00

但是我得到了:

42 4D 40 00 46 00 00 00 00 00 00 00 36 00 00 00

首先它应该只包含 14 个字节。那个“40 00”毁了这一切。这是在C中设置标题的正确方法吗?我还能如何限制输出的字节大小?

【问题讨论】:

  • 编译器通常在结构字段之间放置填充。您不能将整个结构写入文件并期望它直接写入字段。如果你打印 sizeof(struct HEADER) 你会发现它比每个字段的总和长。您还应该使用宽度固定的类型,例如 uint32_t 和 uint16_t 而不是 int 和 short。
  • 我现在使用的是固定宽度的类型。如果我不能只编写文件的整个结构并期望它完全按照我告诉计算机的内容编写,那么在编写文件时编程有什么用?说真的,BMP 图像有一个固定的结构,我研究并了解了它是如何工作的,但是我的编译器将不需要的数据推送到我的文件中,不再使其适合 BMP 结构和协议。为填充添加了 4 个字节破坏的文件...
  • 不是重复的,因为写作不像阅读。
  • @RFiischer 回想一下,在定义 BMP 文件时,许多 PC 使用 16 位 ints 的软件。想想当你使用 32 位字段创建一个成功的结构时,一些未来的程序员会被 64 位对齐所困扰。

标签: c bmp


【解决方案1】:

struct 可能在字段之间包含 填充 字节,以将下一个字段与某些地址偏移对齐。这些填充字节的值是不确定的。典型的布局可能如下所示:

struct {
    uint8_t field1;
    uint8_t <padding>
    uint8_t <padding>
    uint8_t <padding>
    uint32_t field2;
    uint16_t field3;
    uint8_t <padding>
    uint8_t <padding>
};

&lt;padding&gt; 只是编译添加的;您的程序无法访问它。 这只是一个例子。实际填充可能不同,由 ABI 为您的架构(CPU/OS/工具链)定义。

此外,较大类型的字节存储在内存中的顺序(endianess)取决于架构。但是,由于文件需要特定的字节顺序,这可能也必须修复。

一些 - 但不是全部 - 编译器允许将 struct 指定为 packed(避免填充),但仍然无法解决字节序问题。

最好是通过移位正确序列化结构并存储到uint8_t-array:

#include <stdint.h>

/** Write an uint16_t to a buffer.
 *
 *  \returns The next position in the buffer for chaining.
 */
inline uint8_t *writeUInt16(uint8_t *bp, value)
{
    *bp++ = (uint8_t)value;
    *bp++ = (uint8_t)(value >> 8);
    return bp;
}

// similar to writeUInt16(), but for uint32_t.
... writeUInt32( ... )
    ...

int main(void)
{
    ...

    uint8_t buffer[BUFFER_SIZE], *bptr;

    bptr = buffer;
    bptr = writeUInt16(bptr, 0x4D42U);    // FileType
    bptr = writeUInt32(bptr, 70U);        // FileSize
    ...

}

这将用标题字段填充bufferBUFFER_SIZE 必须根据您要创建的标头进行设置。存储所有字段后,将buffer 写入文件。

声明函数inline 暗示一个好的编译器可以为常量创建几乎最佳的代码。

还要注意,short 等的大小不是固定的。使用stdint.h 类型是您需要定义大小的类型。

【讨论】:

  • 我不明白你的解决方案,但我明白问题出在哪里。
  • @RFiischer:您有什么不明白的地方吗?也许我可以加强我的回答。
  • 我认为问题在于我缺乏知识。例如,什么是“缓冲区”?我不期待答案。现在困扰我的是,当编译器向文件写入任何包含“10”或“0A”的内容时,它会写下“0D 0A”。
  • bufferuint8_t 的数组。在你写的时候,你显然传递了一个字符串文字,你应该传递一个整数(变量或文字)。 writeUInt... 函数只采用相应的整数类型(具有明确定义的宽度)并将它们按字节放入数组中。
【解决方案2】:

问题是你的结构是对齐的。你应该这样写

#pragma pack(push, 1)
typedef struct HEADER {
    short FileType;
    int FileSize;
    short R1;
    short R2;
    int dOffset;
} tp_header;
#pragma pack(pop)

只是为了让您知道-默认情况下,出于优化原因的编译器会将其布局为:

typedef struct HEADER {
    short FileType;
    char empty1; //inserted by compiler
    char empty2; //inserted by compiler
    int FileSize;
    short R1;
    short R2;
    int dOffset;
} tp_header;

但实际上你还犯了另一个错误:sizeof(int) ≥ 4 bytes。 IE。取决于平台整数可能是 8 个字节。重要的是,在这种情况下,您必须使用像 int32_t from cstdint 这样的类型

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 2016-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多