【问题标题】:Byte padding issue on cross arch交叉拱上的字节填充问题
【发布时间】:2011-09-06 06:23:18
【问题描述】:

我注意到我的程序结构上的 sizzeof() 在 x86 和 x64 平台上是不同的。这是因为字节填充。由于一个要求(我的应用程序在跨拱 m/c 之间进行通信),我需要确保目标应该获得与发送者通过命名管道发送的相同大小的结构(在我的情况下,我无法再次读取管道剩余数据..)。如果我可以在该结构上使用 sizeof() 运算符之前安全地禁用/启用填充或剥离填充字节,我需要一种 C++ 方法。

谢谢..

[编辑 |结论]:只是为其他试图为类似问题寻求解决方案的人提供的输入。尝试了很多愚蠢的事情来解决这个问题,但这里提到的一个可能的解决方案以另一种方式产生了问题,调试哪个时间。我能找到的解决这个问题的最佳选择是使用下面提到的“R..”序列化和反序列化方法。

【问题讨论】:

  • 你确定这只是一个填充问题吗?通常sizeof 的结果(除了sizeof(char))取决于平台。
  • 那么它可能是什么。 x64 上的相同结构,sizeof 运算符显示“336”字节,而 x86 架构显示“316”字节。除了填充之外,我想不出其他原因。
  • 除了填充之外还有一件事,有些类型在 x86 和 x64 之间的大小不同。 longvoid *size_t 及其所有派生类型都有不同的大小。

标签: c++ c cross-platform padding named-pipes


【解决方案1】:

如果您在结构之前键入#pragma pack(1),它应该禁用填充。这适用于视觉工作室和 gcc。您可能还想使用#pragma pack(push) 和#pragma pack(pop) 来保存和恢复以前的填充规则。 例如:

#pragma pack(push)
#pragma pack(1)
struct
{
...
};
#pragma(pop)

【讨论】:

  • 这更像我想要的。我会试一试。谢谢
  • 这是一个非常糟糕的主意。您应该改为编写正确的序列化和反序列化方法。不正确地打包结构会导致代码损坏,除非你非常小心......
  • 但我认为序列化与内存填充无关。我错了吗??
  • 我认为他的意思是,您可以使用 WriteUInt16(...) 之类的函数对每个单独的成员进行流式传输,而不是流式传输整个结构,另一方面,您可以使用 ReadInt16。这样您就可以控制一切,而填充无关紧要。
  • 它们可能对您有用,但请记住,在许多架构上,如果您尝试访问未对齐的数据,您会收到错误消息。因此,您最好将对齐设置为适合您所有架构的值,而不是 (1)
【解决方案2】:

避免编写使用结构/联合的 sizeof 的代码。为了便于携带,请在单个成员上使用 sizeof:

const int struct_size = sizeof(x.member1) + sizeof(x.member2) + ...;

【讨论】:

  • 但是如果会员的数据大小在两个平台上不同..??
  • 推测他正在通过套接字传输这些结构,因此他确实需要确保套接字的两端在结构中所有字段的大小和位置上达成一致。我认为这个答案不能解决这个问题。也许你对这个问题有不同的理解?
  • 这可能是一种可能性,但在我的情况下,结构有点长(15 个元素),我认为这不是更清晰的方法。如果没有其他更清洁的方法,我可能会采取这种方法并接受你的回答,作为最后的手段。
  • 如果您将结构作为数组发送,这种方法会出现问题。为此,使填充和大小保持一致是不可避免的,除非您完全摆脱结构并使用每个成员字段的数组。
  • 这是没有问题的方法。在特定编译器上禁用填充是不可移植的。它有什么不干净的地方?您必须在程序中再添加 15 行……所以? sizeof 在编译时进行评估,因此您可以将整个内容隐藏在实际程序之外,只使用常量。
【解决方案3】:

你用的是什么编译器?您可以使用 GCC 的 packed 属性告诉编译器不要在您的结构中添加任何填充。其他编译器可能也有类似的功能。

http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html

aligned 属性也可能对您有所帮助。

【讨论】:

  • 在特定编译器上禁用填充不会使代码具有可移植性。
  • 这不是重点......重点是让编译器以正确/可预测的方式处理结构,以便您的可执行文件可以通过套接字与其他实例正确发送和接收结构。原始发布者在他的问题中没有使用“便携式”这个词,他只是希望他的程序能够工作。无论如何,您可以通过为每个要支持的编译器添加#ifdefs 来使其可移植。
  • 为什么要摆弄编译器选项而不是编写可移植的 C 代码呢? C 标准明确规定,编译器可以在结构中的任何位置添加任意数量的填充字节,除了第一个元素之前。编写不对填充和对齐做任何假设的代码并不难。
  • 如何通过套接字发送整个结构?一次一个领域?我想这可行,但是您必须维护结构中所有字段的多个列表。如果您可以将整个结构作为二进制数据块发送,那就更好了,即从(char *)mystruct 开始,大小为sizeof(mystruct)。为此,您需要控制所有字段的放置位置。我的回答显示了如何做到这一点。
  • 除非我确定我的代码永远不会被移植,否则我不会将结构用于数据协议。如果需要可移植性,我会为协议使用一个字节数组。如果程序员不能处理该数组但非常渴望结构,则必须编写一些用于在数组和结构之间打包/解包的代码。在编写可移植代码时,您通常会得到这样的打包代码,因为大多数数据协议都指定了特定的字节序。
【解决方案4】:

请记住还要确保您使用的是与拱无关的结构元素 - 如果两个不同的拱具有 - 比如说 - 不同大小的 int,那么删除填充将无济于事。尝试使用 stdint.h 类型,例如。 int32_t 而不是 int。

【讨论】:

    【解决方案5】:

    其他人已经指出,除了填充之外,类型的大小可能会因架构而异。除此之外,字节序也可能有所不同(不在 32 位和 64 位英特尔之间,但如果抛出其他字节,则可能是个问题)。

    所以序列化是个好主意。基本上只需为每个架构创建一个用于写入的函数和一个用于读取的函数。

    write 函数将写入每个结构成员的每个相关字节,成员以已知顺序写入,每个成员的字节也以已知顺序(例如,最重要的在前)。如果类型的大小因平台而异,请写入所有平台支持的类型的字节数。

    读取函数应该与写入函数相反,并将未包含在序列化数据中的任何字节归零。这些函数并不难编写,使用它们会使基本类型的填充、字节顺序和(通常)大小变得无关紧要。

    【讨论】:

    • 此外,数据协议本身通常会指定字节序。然而,在这个问题中,Windows 管道并非如此。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-06
    • 2013-01-19
    • 2010-10-20
    • 2013-01-22
    • 2011-03-28
    相关资源
    最近更新 更多