什么时候结构包装问题会突然出现?

时间:2012-09-01 17:17:53

标签: c++ struct

我一直在阅读关于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 个答案:

答案 0 :(得分:3)

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

因此,如果您将该结构的字节写入一种系统上的文件,将该文件传输到另一种系统,并将其读回,那么您将在data2中看到不同的值

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

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

由于存在这些差异,因此将结构直接写入文件(或套接字)通常是不合法的。您可以在特定情况下使用它,在这种情况下,任何读取它的人都具有与该结构完全相同的内存表示,这意味着如果您首先确定哪些字节到达具有什么含义的位置,则可以执行此操作,然后确保所有需要读/写文件的程序可以使用该格式。

答案 1 :(得分:2)

包装规则(类似地,endianness)可能会成为考虑因素,包括您的示例,何时

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

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

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

对于Microsoft编译器,可以使用pragma pack,对于gcc,可以使用__attribute__ ((__packed__))

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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