正在将文件读取到结构(C++)中

Reading file into a struct (C++)

本文关键字:C++ 结构 文件 读取      更新时间:2023-10-16

我正在尝试从二进制文件中读取数据,并将其放入结构中。data.bin的前几个字节是:

03 56 04 FF FF FF ...

我的实现是:

#include <iostream>
#include <fstream>
int main()
{
    struct header {
        unsigned char type;
        unsigned short size;
    } fileHeader;
    std::ifstream file ("data.bin", std::ios::binary);
    file.read ((char*) &fileHeader, sizeof header);
    std::cout << "type: " << (int)fileHeader.type;
    std::cout << ", size: " << fileHeader.size << std::endl;
}

我期望的输出是type: 3, size: 1110,但由于某种原因,它是type: 3, size: 65284,所以基本上跳过了文件中的第二个字节。这里发生了什么?

实际上,行为是由实现定义的。实际情况可能是,在结构的type成员之后有一个1字节的填充,然后在第二个成员size之后。我是在看到输出后提出这个论点的。

这是您的输入字节:

03 56 04 FF FF FF

第一个字节03转到结构的第一个字节,即type,您可以看到这个3作为输出。然后,下一个字节56转到作为填充的第二个字节,因此被忽略,然后下一个两个字节04 FF转到结构的下两个字节,即size(其大小为2字节)。在little-endian机器上,04 FF被解释为0xFF04,它只不过是作为输出的66284

你基本上需要一个紧凑的结构来压缩填充。使用#pragma包。但是与普通结构相比,这样的结构会比较慢。一个更好的选择是手动将结构填充为:

char bytes[3];
std::ifstream file ("data.bin", std::ios::binary);
file.read (bytes, sizeof bytes); //read first 3 bytes
//then manually fill the header
fileHeader.type = bytes[0];
fileHeader.size = ((unsigned short) bytes[2] << 8) | bytes[1]; 

写最后一行的另一种方法是:

fileHeader.size = *reinterpret_cast<unsigned short*>(bytes+1); 

但这是实现定义的,因为它取决于机器的端序。在little-endian机器上,它很可能会工作。

一种友好的方法是(定义实现):

std::ifstream file ("data.bin", std::ios::binary);
file.read (&fileHeader.type, sizeof fileHeader.type);
file.read (reinterpret_cast<char*>(&fileHeader.size), sizeof fileHeader.size);

但是,最后一行取决于机器的端序。

嗯,它可能是结构填充。为了使结构在现代体系结构上快速工作,一些编译器会在其中添加填充,以保持它们在4或8字节的边界上对齐。

您可以使用杂注或编译器设置来覆盖它。例如,Visual studio的/Zp

如果发生这种情况,那么你会在第一个字符中看到值56,然后它会将接下来的n个字节读取到填充中,然后将接下来的2读取到短字符中。如果第2个字节作为填充丢失,那么接下来的2个字节将被读取到short中。由于short现在包含数据"04 FF",这(以小端序)等于0xff04,即65284。

您可以使用#pragma pack编译器指令来覆盖填充问题:

#pragma pack(push)
#pragma pack(1)
struct header {
    unsigned char type;
    unsigned short size;
} fileHeader;
#pragma pack(pop)

编译器将结构填充到2或4的倍数字节中,以便在机器代码中更容易实现对它们的访问。除非真的有必要,否则我不会使用#pragma包,这通常只适用于在非常低的级别(如固件级别)工作的情况。维基百科上关于这一点的文章。

之所以会发生这种情况,是因为微处理器有特定的操作方式来访问地址为4或2的倍数的内存,这使得源代码更容易制作,它更有效地使用内存,有时代码会更快一点。当然,有一些方法可以阻止这种行为,比如pragma-pack指令,但它们依赖于编译。但是重写编译器默认值通常是个坏主意,编译器人员有充分的理由让它这样做。

对我来说,一个更好的解决方案是用纯C来解决这个问题,这非常非常简单,并遵循良好的编程实践,即:永远不要依赖编译器对低级别数据所做的操作。

我知道,仅仅做#pragma pack(1)就很性感,很简单,让我们都感觉到我们正在直接处理和理解计算机内部发生的事情,这让每个真正的程序员都很兴奋,但最好的解决方案总是用你使用的语言实现的。它更容易理解,因此更容易维护;这是默认行为,所以它应该在任何地方都能工作,在这种特定的情况下,C解决方案非常简单直接:只需逐个属性读取结构,如下所示:

void readStruct(header &h, std::ifstream file)
{
    file.read((char*) &h.type, sizeof(char));
    file.read((char *) &h.size, sizeof(short));
}

(当然,如果您全局定义结构,这将起作用)

更好的是,当您使用C++时,定义一个成员方法来为您进行读取,然后只调用myObject.readData(file)。你能看到美丽和简单吗?

默认情况下,更容易阅读、维护和编译,可以更快地优化代码。

我通常不喜欢乱用#pragma指令,除非我对自己在做什么很确定。其影响可能令人惊讶。