标准保证的c++二进制数据布局



这纯粹是一个理论问题,我并没有真正陷入其中,但它激发了我的好奇心,想看看是否有人能找到更好的解决方案:

如何便携地保证特定的文件格式/网络协议或符合特定比特模式的任何东西。

假设我们有一种文件格式,它使用一个64位的标头结构,紧接着是一个32位结构的可变长度数组:

Header:  magic : 32 bit
         count : 32 bit
Field :  id   : 16 bit
         data : 16 bit

我的第一直觉是写这样的东西:

struct Field
{
    uint16_t id   ;
    uint16_t data ;
};

除了我们的编译器可能决定填充是可取的,我们最终得到了64位结构。所以我们的下一个赌注是:

using Field = uint16_t[2];

并为此努力。

也就是说,除非有人仔细阅读了标准并注意到uint16_t是可选的。在这一点上,我们的下一个最好的朋友是uint_list16_t,它保证至少16位长,但据我们所知,在10位/char处理器中可能是20位长。

在这一点上,我能想到的唯一真正的解决方案是某种比特流,能够读取和写入特定数量的比特,并可通过std::numeric_limits进行调整。

那么,有没有人非常仔细阅读了标准,发现了我遗漏的地方?或者,这是拥有可移植担保的唯一真正方式。

注意事项:-我刚刚意识到,endianness可能会增加另一层复杂性。-我使用的是ISO标准(N3797)的当前工作草案。

如何便携地保证特定的文件格式/网络协议或符合特定比特模式的任何东西。

你不能。C++不是这样的,它是针对一个抽象平台进行标准化的,在这个平台上,可以假设只存在一个由位组成的"字节"。我们甚至不能确定,在标准中只查看时,char中有多少位。您可以对所有内容使用位字段,因为位是不可分割的,但您至少要处理填充。

有时,为了一致性,最好放弃绝对标准一致性的想法,而寻求其他方法来高效地完成工作。在这种情况下,平台细节与几乎绝对标准一致性(又称良好编程实践)相结合将使您获得自由。

我经常使用的每个平台(linux和windows)都提供了一种方法来调节编译器实际应用的填充。对于网络通信,在Linux&我使用的窗口:

#pragma pack (push, 1)

作为我将要发送的所有数据结构的序言。持久性确实是另一个挑战,但使用每个平台提供的其他资源(ntohl等)或多或少都很容易解决。

标准一致性是一个值得称赞的目标,事实上,在代码审查中,我会拒绝大多数不一致的代码。然而,缺乏一致性实际上只是拒绝的一个绰号;而不是原因本身。拒绝的实际原因在很大程度上是在迁移到另一个平台时,维护和移植不一致的代码很困难,甚至只是在同一平台上升级编译器。不符合要求的代码可能会编译,甚至看起来可以工作,但它往往会以微妙而痛苦的方式失败,即使在彻底测试之后也是如此。

这个故事的寓意是:

您应该始终编写符合标准的代码,除非不应该。

这实际上只是爱因斯坦对奥卡姆剃刀的重新想象:

让一切尽可能简单,但不要简单。

如果您想确保所有符合标准的东西的可移植性,包括CHAR_BITS不是8的平台,那么,您已经做好了工作。

如果你愿意将自己限制在98%的计算机上,我建议你为任何必须遵守特定有线格式的东西编写显式序列化。这包括将整数分解为字节等。

围绕事物编写适当的抽象,代码就不会太糟糕。不要把轮班和口罩放在任何地方。封装它。

我会使用网络类型和网络字节顺序。请参阅此链接。http://www.beej.us/guide/bgnet/output/html/multipage/htonsman.html.该示例使用uint16_t。您可以一次将值写入一个字段以防止填充。或者,如果您想一次读取和写入整个结构,请参阅以下链接C++结构对齐问题

使结构易于程序使用。

提供从输入中提取数据并写入数据成员的输入方法。这消除了填充、对齐边界和端序的问题。与输出类似。

例如,如果输入数据为16位宽,但平台为32位宽,请使用32位字段声明结构。将输入中的16位复制到32位字段中。

大多数程序读取结构的次数少于访问数据成员的次数。您的程序没有100%读取输入。

最新更新