0

我一直在阅读有关 struct bit 打包顺序问题的信息,但由于曝光有限,我自己没有遇到过。但是,我注意到这些讨论主要针对非常复杂的应用程序。

我现在正在编写一个结构来保存来自 ifstream 这样的信息

struct MyFileStruct
{
    char data1[40];
    int data2;
    char data3[12];
    // etc..
};

ifstream fin;
// .. snip ..
fin.read((char*)&myfilestruct, sizeof(MyFileStruct));

只是想如果在这个简单的场景中是否会出现任何问题,可能是在另一个操作系统或 32/64 位架构中。等等。那么,确切地说,什么时候会考虑位包装顺序?

4

4 回答 4

3

使用该特定结构,您最有可能遇到的问题是字节序。在 little-endian 系统上,最低地址字节int包含最低有效 8 位。在大端系统上,最高有效 8 位。

因此,如果您将该结构的字节写入一种系统上的文件,将文件传输到另一种系统,然后将其读回,那么您将在data2.

但是,您可能会在使用其他结构或使用不寻常的系统/编译器时遇到其他问题:

  • 基本类型的大小 -int通常是 4 个字节,但不是必须的。long在不同的常见系统上具有不同的大小(Windows 上为 4 字节,64 位 Linux 上为 8 字节)。显然,如果您尝试从文件中读取结构,并期望与其他 C++ 实现实际写入的字节数不同,那么您就有问题了。
  • padding - 允许编译器将未使用的字节放入成员之间的结构中。这通常发生以确保对齐。例如,在许多编译器中,int成员的偏移量始终是 4 的倍数。由于 40 在您的结构中无论如何都是 4 的倍数,这不会有任何区别,但如果第一个数组是 39 字节,那么实现对齐int会插入一个未使用的字节,而没有插入的则不会。在某些 CPU(例如 x86)上,int对齐是有帮助的,但不是必需的,在这种情况下,编译器通常有办法注释一个结构来说明是否填充它。

由于存在这些差异,通常将结构直接写入文件(或套接字)是不合法的。您可以在特定情况下摆脱它,即读取它的人对该结构具有完全相同的内存表示,这意味着如果您首先准确计算出哪些字节具有什么含义,然后确保所有需要读/写文件的程序可以使用该格式。

于 2012-09-01T17:46:55.710 回答
2

遇到问题的一个典型示例是序列化结构数组。假设结构大小为 12,但它被打包到 4 字节边界或 8 字节边界。如果磁盘上有一个包含 2 个元素的数组,则第一个元素将从偏移量 0 开始。第二个将从位置 12(如果要打包到 4 字节边界)或位置 16(如果要打包到 8 字节边界)开始。因此,当您读取数组时,第一个元素会正确出现,但第二个(以及后续元素)可能会被弄乱。

请注意,在 Visual Studio 中,对于 32 位和 64 位编译,默认打包为 8 字节边界,因此您可能会很幸运并且没有问题。但是,如果您想看到这一点,请将您的 32 位编译器设置为与 4 字节边界对齐,并将您的 64 位编译器设置为与 8 字节边界对齐(例如)。然后像上一段一样创建一个结构数组。

于 2012-09-01T18:01:34.590 回答
1

打包规则(以及类似的字节序)可能会成为考虑因素,包括您的示例,当

  • 该程序是用不同的编译器编译的
  • 程序是用同一个编译器的不同版本编译的(理论上)
  • 该程序使用不同的编译器选项进行编译,包括但不限于更改目标操作系统、目标硬件或 32/64 位设置
  • 编译器指令被添加到源代码中,例如#pragma pack.

一个安全的一般规则是,只有当读取您的结构的可执行文件是由相同的可执行文件编写时,您的代码才能保证工作

当这是一个问题时,打包问题(但不是字节顺序)的一种常见解决方案是使用非标准编译器指令以效率为代价来删除打包。

这可以通过pragma packMicrosoft 编译器和__attribute__ ((__packed__))gcc 来完成。

于 2012-09-01T17:38:34.477 回答
0

一般规则是您可以读取使用由同一编译器编译的代码编写的文件(并且包括编译器选项)。最简单的形式是写出二进制数据的程序,以便以后可以将其读回。除此之外,您将进入特定于实现的行为,并且没有简单的答案。

于 2012-09-01T17:23:11.797 回答