正在将文件读取到结构(C++)中
Reading file into a struct (C++)
我正在尝试从二进制文件中读取数据,并将其放入结构中。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指令,除非我对自己在做什么很确定。其影响可能令人惊讶。
- 如何循环打印顶点结构
- 通过方法访问结构
- 使用不带参数的函数访问结构元素
- 预处理器:插入结构名称中的前一个行号
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 孤立代码块在结构中引发异常
- 有什么方法可以遍历结构吗
- 如何在 C# 中映射双 C 结构指针?
- 如何在C++中使用结构生成映射
- 无法将结构注册为增强几何体3D点
- 多成员Constexpr结构初始化
- C++将文本文件中的数据读取到结构数组中
- 如何重构类层次结构以避免菱形问题
- 如何在C++中序列化结构数据
- std::vector的包装器,使数组的结构看起来像结构的数组
- 没有为自己的结构调用列表推回方法
- 奇怪的结构&GCC&clang(void*返回类型)
- 在 c++ 中拥有一组结构的正确方法是什么?
- vscode g++链路故障:体系结构x86_64的未定义符号
- C++概念:如何使用'concept'检查模板化结构的属性?