读取结构定义的二进制文件

时间:2009-05-15 12:56:27

标签: c++ data-structures binary

有人能指出我如何读取由C结构定义的二进制文件的正确方向吗? 它在结构体内部有一些#define,这使我的事情变得复杂 结构看起来像这样:(虽然它比它更大,更复杂)

struct Format {
    unsigned long str_totalstrings;
    unsigned long str_name;
    #define STR_ORDERED 0x2
    #define STR_ROT13 0x4
    unsigned char stuff[4];
    #define str_delimiter stuff[0]
}

如果有人能指出我如何做到这一点,我真的很感激。或者如果那里有任何涵盖这个主题的教程?

非常感谢您的帮助。

5 个答案:

答案 0 :(得分:5)

有一些不好的想法和好主意:

这是一个坏主意:

  • 将一个原始缓冲区强制转换为struct
    • 解析整数> 1个字节长或浮点数时有endianness个问题(little-endian vs big-endian)
    • 结构中有byte alignment issues,它们非常依赖于编译器。可以尝试禁用对齐(或强制执行一些手动对齐),但它通常也是一个坏主意。至少,通过使CPU访问未对齐的整数,您将破坏性能。内部RISC核心必须执行3-4次操作而不是1次(即"在第一个单词中执行第1部分","在第二个单词中执行第2部分","合并结果")每次访问它。或者更糟糕的是,用于控制对齐的编译器编译指示将被忽略,并且您的代码将会中断。
    • 对于C / C ++中的常规intlongshort等类型,没有确切的大小保证。您可以使用int16_t之类的内容,但这些内容仅适用于现代编译器。
    • 当然,当使用引用其他结构的结构时,这种方法会完全破坏:必须手动将它们全部展开。
  • 手动编写解析器:它比第一眼看上去困难得多。
    • 一个好的解析器需要在每个阶段进行大量的健全性检查。很容易错过一些东西。如果你不使用例外,那就更容易错过。
    • 如果您的解析代码不是异常安全的,那么使用异常会使您容易失败(即以某种方式编写它可以在某些时候中断并且它不会泄漏内存/忘记最终确定某些对象)< / LI>
    • 可能存在性能问题(即执行大量无缓冲IO而不是执行一个操作系统read系统调用然后解析缓冲区 - 反之亦然,一次读取整个内容而不是更精细,懒惰的读取位置#39;适用)。

是个好主意
  • 跨平台。几乎不言自明,近年来所有的移动设备,路由器和物联网都在蓬勃发展。
  • 去声明。考虑使用任何声明性规范来描述您的结构,然后使用解析器生成器来生成解析器。

有几种工具可以做到:

  • Kaitai Struct - 我最喜欢的,跨平台,跨语言 - 即你描述你的结构一次,然后你可以将它编译成C ++,C#,Java,Python,Ruby,PHP的解析器,等
  • binpac - 非常过时,但仍然可用,仅限C ++ - 与意识形态中的开泰相似,但自2013年以来未得到支持
  • Spicy - 据说是#34;现代改写&#34; binpac,AKA&#34; binpac ++&#34;,但仍处于早期开发阶段;可用于较小的任务,也仅用于C ++。

答案 1 :(得分:4)

读取结构定义的二进制文件很简单。

Format myFormat;
fread(&myFormat, sizeof(Format), 1, fp);

#defines根本不会影响结构。 (尽管如此,里面是一个奇怪的地方)。

但是,这不是跨平台安全的。在确保读者和作者使用相同平台的情况下,这是最简单的事情。

更好的方法是重新定义您的结构:

struct Format {
    Uint32 str_totalstrings;  //assuming unsigned long was 32 bits on the writer.
    Uint32 str_name;
    unsigned char stuff[4];
};

然后有一个'platform_types.h',它可以为你的编译器正确输入Uint32。现在你可以直接读到结构,但是对于endianness问题你仍然需要 做这样的事情:

myFormat.str_totalstrings = FileToNative32(myFormat.str_totalstrings);
myFormat.str_name =   FileToNative32(str_name);

其中FileToNative是无操作或字节反转器,具体取决于平台。

答案 2 :(得分:2)

如果您有要在内存中解析的数据,也可以使用联合进行此解析。

union A {
    char* buffer;
    Format format;
};

A a;
a.buffer = stuff_you_want_to_parse;

// You can now access the members of the struct through the union.
if (a.format.str_name == "...")
    // do stuff

还要记住,不同平台上的长度可能不同。如果您依赖于long一定大小,请考虑使用int stdint.h中定义的类型,例如uint32_t。

答案 3 :(得分:2)

使用C ++ I / O库:

#include <fstream>
using namespace std;

ifstream ifs("file.dat", ios::binary);
Format f;
ifs.get(&f, sizeof f);

使用C I / O库:

#include <cstdio>
using namespace std;

FILE *fin = fopen("file.dat", "rb");
Format f;
fread(&f, sizeof f, 1, fin);

答案 4 :(得分:0)

您必须找到写入文件的机器的endiannes,以便正确解释整数。注意ILP32与LP64的不匹配。原始结构包装/对齐可能也很重要。