我正在尝试从二进制文件中读取数据并将其放入结构中。
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
,所以基本上跳过了文件中的第二个字节。这里发生了什么?
答案 0 :(得分:7)
实际上,行为是实现定义的。在您的情况下实际发生的情况可能是,在结构的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);
但这是实现定义的,因为它取决于机器的endian-ness。在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);
但是,最后一行取决于机器的字节序。
答案 1 :(得分:1)
好吧,它可能是struct padding。为了使结构在现代体系结构上快速运行,一些编译器会在其中放置填充以使它们在4或8字节边界上对齐。
您可以使用编译指示或编译器设置覆盖它。例如。 Visual Studio的/Zp
如果发生了这种情况,那么你会在第一个字符中看到值56,然后它会读取填充中的下一个n字节,然后将下一个字节读入短消息中。如果第二个字节作为填充丢失,则接下来的2个字节将被读入短路。由于short现在包含数据'04 FF',因此(小端)等于0xff04,即65284。
答案 2 :(得分:0)
编译器将结构填充到2或4的倍数的字节,以便在机器代码中更容易实现对它们的访问。我不会使用#pragma pack,除非它确实是必要的,而且通常只适用于你真正低级别的工作(如固件级别)。 Wikipedia article on that.
发生这种情况是因为微处理器具有特定的操作来访问四到两个地址的内存,这使得源代码更容易制作,它更有效地使用内存,有时代码更快一些。有一些方法可以阻止这种行为,当然比如pragma pack指令,但它们依赖于编译。但是要覆盖编译器默认值通常是一个坏主意,编译器人员有一个很好的理由让它以这种方式运行。对我来说,更好的解决方案是使用纯C来解决这个问题,这非常非常简单,并且遵循一个良好的编程习惯,即:永远不要依赖编译器在低级别对数据执行的操作。
我知道只是做#pragma pack(1)是性感的,简单的,让我们感觉到我们正在直接处理和理解计算机内容上发生的事情,这会让每个真正的程序员都开启,但是最好的解决方案始终是使用您正在使用的语言实现的解决方案。它更容易理解,因此更容易维护;它是默认行为,因此它应该可以在任何地方使用,并且在这种特定情况下,C解决方案非常简单直接:只需按属性读取struct属性,如下所示:
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指令,除非我非常肯定我在做什么。其影响可能令人惊讶。
答案 3 :(得分:0)
您可以使用#pragma pack
编译器指令来覆盖填充问题:
#pragma pack(push)
#pragma pack(1)
struct header {
unsigned char type;
unsigned short size;
} fileHeader;
#pragma pack(pop)