解析二进制文件。 什么是现代方式?

我有一个我知道的布局的二进制文件。 例如让格式如下所示:

  • 2个字节(无符号短整数) - 一个字符串的长度
  • 5个字节(5个字符) - 字符串 - 一些id名称
  • 4个字节(无符号整数) - 一个步幅
  • 24个字节(6个浮点数 - 每个浮点数2个浮点数) - 浮点数据
  • 该文件应该看起来像(为了便于阅读,我添加了空格):

    5 hello 3 0.0 0.1 0.2 -0.3 -0.4 -0.5
    

    这里5 - 是2个字节:0x05 0x00。 “你好” - 5个字节等。

    现在我想读这个文件。 目前我这样做:

  • 加载文件到ifstream
  • 读这个流到char buffer[2]
  • 将其转换为unsigned short: unsigned short len{ *((unsigned short*)buffer) }; 。 现在我有一个字符串的长度。
  • 读一个流向vector<char>并从这个向量创建一个std::string 。 现在我有字符串ID。
  • 以相同的方式读取下4个字节并将它们转换为无符号整型。 现在我迈出了一大步。
  • 而不是文件结束读取以同样的方式浮动 - 为每个浮点创建char bufferFloat[4]和cast *((float*)bufferFloat)
  • 这有效,但对我来说它看起来很丑。 我可以直接读取unsigned shortfloatstring等没有char [x]创建? 如果不是,那么正确投射的方式是什么(我阅读过我使用的这种风格 - 是旧式风格)?

    PS:在我写了一个问题的时候,我头脑中提出的更清晰的解释 - 如何从char [x]任意位置投射任意数量的字节?

    更新:我忘了明确提到字符串和浮点数据长度在编译时是未知的,并且是可变的。


    在C ++中可以正常工作的C方法是声明一个结构体:

    #pragma pack(1)
    
    struct contents {
       // data members;
    };
    

    注意

  • 你需要使用一个编译指示来使编译器将数据对准结构中的外观;
  • 这种技术只适用于POD类型
  • 然后将读取缓冲区直接转换为结构体类型:

    std::vector<char> buf(sizeof(contents));
    file.read(buf.data(), buf.size());
    contents *stuff = reinterpret_cast<contents *>(buf.data());
    

    现在如果你的数据的大小是可变的,你可以分成几个块。 为了从缓冲区中读取一个二进制对象,阅读器功能非常方便:

    template<typename T>
    const char *read_object(const char *buffer, T& target) {
        target = *reinterpret_cast<const T*>(buffer);
        return buffer + sizeof(T);
    }
    

    主要的优点是这样的阅读器可以专门用于更高级的c ++对象:

    template<typename CT>
    const char *read_object(const char *buffer, std::vector<CT>& target) {
        size_t size = target.size();
        CT const *buf_start = reinterpret_cast<const CT*>(buffer);
        std::copy(buf_start, buf_start + size, target.begin());
        return buffer + size * sizeof(CT);
    }
    

    现在在您的主解析器中:

    int n_floats;
    iter = read_object(iter, n_floats);
    std::vector<float> my_floats(n_floats);
    iter = read_object(iter, my_floats);
    

    注意:正如Tony D所观察到的,即使您可以通过#pragma指令和手动填充(如果需要)来获得对齐方式,您仍然可能遇到与处理器对齐方式不兼容的情况,其形式为(最佳情况)性能问题或(最差情况)陷阱信号。 只有在您控制了文件格式的情况下,这种方法才有意思。


    如果它不是为了学习的目的,并且如果你有自由选择二进制格式的话,你最好考虑使用类似protobuf的东西,它将为你处理序列化并允许与其他平台和语言进行互操作。

    如果你不能使用第三方API,你可以看看QDataStream的灵感

  • 文档
  • 源代码

  • 目前我这样做:

  • 加载文件到ifstream

  • 读这个流到字符缓冲区[2]

  • 将其转换为unsigned shortunsigned short len{ *((unsigned short*)buffer) }; 。 现在我有一个字符串的长度。

  • 最后的风险是SIGBUS ,性能和/或排序问题。 我建议读两个字符,然后你可以说(x[0] << 8) | x[1] (x[0] << 8) | x[1] ,反之亦然,如果需要校正字节序,使用htons

  • 读一个流向vector<char>并从这个vector创建一个std::string 。 现在我有字符串ID。
  • 不需要...直接读入字符串:

    std::string s(the_size, ' ');
    
    if (input_fstream.read(&s[0], s.size()) &&
        input_stream.gcount() == s.size())
        ...use s...
    
  • 以相同的方式read下4个字节并将它们转换为unsigned int 。 现在我迈出了一大步。 while不是文件read结束float s同样的方式 - 为每个float创建一个char bufferFloat[4]和cast *((float*)bufferFloat)
  • 更好的直接在读取数据unsigned int S和floats ,作为这样编译器将确保正确对齐。

    这有效,但对我来说它看起来很丑。 我可以直接读取unsigned shortfloatstring等没有char [x]创建? 如果不是,那么正确投射的方式是什么(我阅读过我使用的这种风格 - 是旧式风格)?

    struct Data
    {
        uint32_t x;
        float y[6];
    };
    Data data;
    if (input_stream.read((char*)&data, sizeof data) &&
        input_stream.gcount() == sizeof data)
        ...use x and y...
    

    请注意,上面的代码避免了将数据读入可能未对齐的字符数组中,其中由于对齐问题导致在可能未对齐的char数组(包括std::string )中reinterpret_cast数据是不安全的。 同样,如果文件内容在字节顺序上存在差异,您可能需要使用htonl一些后读转换。 如果存在未知数量的float s,则需要计算并分配足够的存储空间,使其至少有4个字节的对齐方式,然后将Data*目标......将索引值超过y的声明数组大小因为访问地址处的内存内容是分配的一部分,并保存从流中读入的有效float表示。 更简单 - 但有一个额外的读取速度可能会更慢 - 读取uint32_t然后new float[n]读取new float[n]并进一步read入....

    实际上,这种类型的方法可以工作,并且很多低级和C代码完全可以做到这一点。 可能帮助您阅读文件的“清洁”高级库最终必须在内部执行类似的操作。

    链接地址: http://www.djcxy.com/p/40309.html

    上一篇: Parsing a binary file. What is a modern way?

    下一篇: Why can't you use offsetof on non