【问题标题】:Reading binary data from a .bin file into structs in C++将 .bin 文件中的二进制数据读取到 C++ 中的结构中
【发布时间】:2021-12-17 18:49:51
【问题描述】:

我有一组 .bin 文件,其中包含正式指定格式的数据。我确切地知道每个字段有多少字节,例如名称 = 40 字节,版本号 = 2 字节等。 我也知道它们存储在文件中的确切顺序(例如名称,然后是版本号......)。

到目前为止,我可以将文件中的数据加载到 std::vector<unsigned char> 列表中,然后单步执行该数据并根据预期字节数读取字段。

问题是,如果我弄错任何字段(有很多不同的字段),这种方法很长并且容易出错。

我已经查看并与人们讨论了结构打包、指针转换和位字段。我似乎无法让他们一起工作。

如何将数据读入缓冲区,然后在缓冲区上“覆盖”我的结构?然后根据我在结构中给每个值分配的位字段填充所有字段。

位域的问题是我不能接受字符串。

建议或示例代码将不胜感激。如果您只想发表评论,我可以为您提供代码来展示我目前所拥有的以及我正在努力实现的目标。

#include <vector>

int main()
{
    //File data loaded by function call
    std::vector<unsigned char> fileData;

    //How do I cast fileData to be a dataFields type? 
}

struct dataFields 
{
    int ID : 8;
    // Cannot use bit field for string type? 
    std::string name;
    int versionNumber : 16;
    int someOtherValue : 8;
}

由于工作原因,我无法给出我正在处理的确切代码,但我觉得这总结了我在一个简单的庄园中试图做得相当好。

【问题讨论】:

  • 请提供一些例子。请阅读stackoverflow.com/help/mcve 另外,您已经标记了 C 和 C++,请标记您使用的确切语言 - C 或 C++。
  • 制作一个与您对数据的了解相匹配的打包结构(非打包结构没有您可以依赖的特定内存布局)。使用 memcpy 复制数据(这是人们可以同意合法的一种别名方法)。广泛测试。这是您在这个详细程度所能获得的最多建议。
  • 最简单的、不可移植的解决方案是读取一个记录大小的缓冲区并将其转换为结构指针。
  • 为什么要使用字符串类型的位域?在这种情况下,您必须使用具有明确规定大小的原始 C 数组:char name[40]
  • 那么假设您有一个名为ddataFields 实例,并且fileData 向量包含从您的文件加载的二进制数据,您必须使用memcpymemcpy(&amp;d, &amp;fileData[0], sizeof(dataFields))跨度>

标签: c++ struct file-io binaryfiles


【解决方案1】:

如果这部分不受时间/存储效率限制,请考虑使用序列化库来执行此操作。这些库可以将您的对象序列化为 XML 或 JSON 并轻松反序列化。您无需担心字节顺序或 POD 问题。

【讨论】:

    【解决方案2】:

    不,您确实不能对std::string 使用位模式,因为它只包含几个指针,所以无论如何您都不想这样做。

    我在项目中使用的常用方法是为每种记录类型设置 POD 结构。 然后负责{反}序列化的最低层仅在 POD 和字节之间进行转换。任何 C++ 逻辑,例如 std::string 或可变长度 std::vector 都在更高级别处理。

    #include <array>
    #include <type_traits>
    #include <cstdint>
    #include <cstring>
    
    struct Record{
        std::uint8_t ID;
        std::array<char,40> name;
        std::uint16_t versionNumber;
        std::uint8_t someOtherValue;
    };
    
    static_assert(sizeof(Record)==46);
    static_assert(offsetof(Record,name)==1);
    

    在我的世界中,我尝试让Record 尊重每个元素与sizeof(E) 的标准对齐方式。如果需要,您可以添加打包修饰符。在位域之前首选来自&lt;cstdint&gt; 的类型。

    我建议在每个Record 之后放置一堆static_asserts,以验证其布局。否则有一天会有人出现并尝试“清理”代码,破坏一切。它还为读者很好地记录了协议。

    一个缺点是这不容易支持将可变长度成员放在中间或拥有多个成员,但我从来不需要这样做,保持数据包简单。

    另外,我只是决定协议的固定字节序。如果有人需要其他东西,他们有责任传递正确编码的Records 以进行序列化。

    序列化助手:

    template<typename T>
    T read_value(const unsigned char*& ptr){
        static_assert(std::is_standard_layout_v<T>);
    
        T value;
        std::memcpy(&value,ptr,sizeof(T));
        ptr+=sizeof(T);
        return value;
    }
    
    template<typename T>
    void write_value(unsigned char*& ptr, const T& value){
        static_assert(std::is_standard_layout_v<T>);
    
        std::memcpy(ptr,&value,sizeof(T));
        ptr+=sizeof(T);
    }
    

    负责{反}序列化的最低层可能如下所示:

    void deserialize_stream(const unsigned char* bytes){\
        // Output is bunch of POD types.
        auto record1 = read_value<Record>(bytes);
        auto record2 = read_value<Record>(bytes);
    }
    
    void serialize_stream(unsigned char* bytes){
        // Input is a list of POD types to serialize.
        Record record1{1,"Foo",12,42};
        Record record2{2,"Bar",14,28};
    
        write_value(bytes,record1);
        write_value(bytes,record2);
    }
    

    例子

    int main() { 
        // Just a example, CHECK SIZE in real world.
        std::array<unsigned char,1024> buffer;
    
        serialize_stream(buffer.data());
        deserialize_stream(buffer.data());
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-04
      • 1970-01-01
      相关资源
      最近更新 更多