使用 ifstream 将二进制数据读入结构体

10

我正在尝试使用ifstream从文件中读取二进制数据。

具体来说,我正在尝试使用从文件中读取的数据填充此"Header"结构体:

struct Header {
    char id[16];
    int length;
    int count;
};
  1. 现在,如果我以这种方式读取文件,结果正是我想要的:

input.read((char*)&hdr, sizeof(hdr));
  • 但是如果我手动读取结构体中的每个变量,结果就是无意义的:

  • input.read((char*)&hdr.id,     sizeof(hdr.id));
    input.read((char*)&hdr.length, sizeof(hdr.length));
    input.read((char*)&hdr.count,  sizeof(hdr.count));
    
    我的问题是,这里发生了什么使这两种方法返回不同的结果?


    1
    你读取了 lengthcount,它们怎么定义的?或者,它们应该是 hdr.lengthhdr.count 吗? - Chris McGrath
    1
    如果你认为如何将数据写入文件(或者其他人如何写入)与此相关,那么你是对的;确实如此。简短回答:sizeof(Header)要求至少和其成员大小之和一样大,并且在包括实现成员填充时可以更大。当结构体作为整体写入时,成员并不保证彼此相互紧靠,然而你却期望它们是这样的。也就是说,如果你在“写”一个橙子,试图“读”一个桔子袋子不一定会成功。 - WhozCraig
    @WhozCraig 你是说第一种读取数据的方法可行,而第二种不可行是因为结构体中变量之间有填充吗?我不知道这个特定的数据是怎样写入的,我只知道应该如何读取它,所以让我感到好奇的是为什么它被以这样一种方式写入,第一种读取方法却完美地奏效了。 - Dan
    2
    @Dan,你“只知道应该如何阅读”。那是怎样的呢?第一种方法假设编写此代码的人使用了与您相同的布局和填充。如果没有填充问题,则第二种方法将起作用(并且几乎是开始执行此操作的首选方式,无论是阅读还是编写)。鉴于该结构体中数据成员的布局和大小,如果在元素之间、最后一个元素之后或两者之间存在填充,那么这将是奇怪的,但我见过更奇怪的事情。sizeof(hdr)sizeof()每个成员进行比较将会有所帮助。 - WhozCraig
    1
    @Dan 不要总是指望它会一直保持这样。你很幸运填充和字节序不是问题,因为它们通常都是问题。 - WhozCraig
    显示剩余3条评论
    2个回答

    12

    还有一种方法可以一步读取结构体。

    即:fh.read((char*)&h, sizeof(Header));


    3
    使用这种方法时,必须注意避免内存对齐填充。参见结构体填充和压缩 - Jean-Christophe
    @Jean-Christophe 谢谢,这似乎非常重要。你能给我们一个简单的例子来说明上述代码失败的情况吗?相反,似乎被接受的答案中的代码存在你提到的问题(结构填充):如果结构被填充,逐个元素从中读取可能会因为填充内存而导致损坏,不是吗?(除非 fstream::read 在移动位置时包括填充内存) - undefined
    @starriet 这个不是可移植的。如果文件最初是在不同的指令集上进行序列化的话,上述代码将导致读取错误。一个典型的情况是从32位机器发送消息到64位机器。另一种失败的情况是在打包数据上读取未打包的结构/标量。 - undefined

    9

    如上面的评论所述,您可能缺少hdr.length和hdr.count。 我已经尝试使用gcc 4.8和clang 3.5,它可以正常工作。

    #include <iostream>
    #include <fstream>
    
    #pragma pack(push, r1, 1)
    struct Header {
        char id[15];
        int length;
        int count;
    };
    #pragma pack(pop, r1)
    
    int main() {
      Header h = {"alalalala", 5, 10};
    
      std::fstream fh;
      fh.open("test.txt", std::fstream::out | std::fstream::binary);
      fh.write((char*)&h, sizeof(Header));
      fh.close();
    
      fh.open("test.txt", std::fstream::in | std::fstream::binary);
    
      fh.read((char*)&h.id, sizeof(h.id));
      fh.read((char*)&h.length, sizeof(h.length));
      fh.read((char*)&h.count, sizeof(h.count));
    
      fh.close();
    
      std::cout << h.id << " " << h.length << " " << h.count << std::endl;
    }
    

    1
    id 字段更改为 char id[15],然后再次运行,只是为了好玩。 - WhozCraig
    你是对的。添加了#pragma指令来修复对齐问题。 - Blaz Bratanic
    如果处理字节顺序,这将会得到很大的改善。如果这是在大端系统上编写并在小端系统上读取,那么情况会变得非常糟糕。 - Jamie

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接