GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?

Posted

技术标签:

【中文标题】GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?【英文标题】:Does GCC's __attribute__((__packed__)) retain the original ordering?GCC 的 __attribute__((__packed__)) 是否保留原始顺序? 【发布时间】:2010-12-17 22:15:44 【问题描述】:

目的

我正在用 C 语言编写一个网络程序(特别是 gnu89),我想通过将某个 struct X 重新解释为大字节数组(又名 char)来简化事情,通过网络发送字节,并在另一边将它们重新解释为struct X。为此我决定使用 gcc 的 __attribute__((__packed__ ))。我已尽最大努力确保正确完成此操作(即我已经考虑了字节顺序和其他相关问题)。

问题

除了保证struct X 尽可能小,gcc 是否保证用 __attribute__((__packed__ )) 定义的struct 保留原始顺序?我进行了大量搜索,但尚未找到任何有关此保证是否存在的文档。

备注

可以安全地假设发送者和接收者都不会遇到可移植性问题(例如,服务器上的 sizeof(int) 等于客户端上的 sizeof(int))。

【问题讨论】:

“原始订单”是什么意思?您的意思是结构成员的字面顺序与定义中指定的顺序相同吗? 鉴于您正在编写网络程序,当您的数据进入使用 SPARC 或 PowerPC 芯片(仅举两个系列)的机器时,您可能会陷入痛苦的境地,而来源是英特尔芯片。此外,如果您使用打包结构加载和发送数据之外的任何其他操作,访问打包数据所带来的性能损失可能比您编写代码以平台中立的方式从解压缩数据结构中序列化数据要差得多。此外,精心设计的数据结构中不会有随机漏洞——这是可以做到的。 +1 乔纳森的上述评论。重新设计您的结构,使其在第一行中没有孔。 (从最大到最小的元素通常就足够了。) @Jonathan Leffler 我写这个程序仅供个人使用。我事先知道连接机器的架构。 【参考方案1】:

是的,__attribute__((packed))(不需要第二组下划线)是实现二进制(即非文本)网络协议的正确方法。元素之间不会有间隙。

不过你应该明白packed不仅打包了结构,而且:

使其所需的对齐为一个字节,并且 确保其成员(可能因打包和结构本身缺乏对齐要求而未对齐)被正确读取和写入,即其字段的未对齐由编译器在软件中处理。

然而,如果你直接访问结构成员,编译器只会处理错位。您应该永远不要将指针指向打包结构的成员(除非您知道该成员所需的对齐方式为 1,例如 char 或其他打包结构)。以下 C 代码演示了该问题:

#include <stdio.h>
#include <inttypes.h>
#include <arpa/inet.h>

struct packet 
    uint8_t x;
    uint32_t y;
 __attribute__((packed));

int main ()

    uint8_t bytes[5] = 1, 0, 0, 0, 2;
    struct packet *p = (struct packet *)bytes;

    // compiler handles misalignment because it knows that
    // "struct packet" is packed
    printf("y=%"PRIX32", ", ntohl(p->y));

    // compiler does not handle misalignment - py does not inherit
    // the packed attribute
    uint32_t *py = &p->y;
    printf("*py=%"PRIX32"\n", ntohl(*py));
    return 0;

在 x86 系统上(不强制内存访问对齐),这将产生

y=2, *py=2

正如预期的那样。另一方面,例如在我的 ARM Linux 板上,它产生了看似错误的结果

y=2, *py=1

【讨论】:

【参考方案2】:

假设您询问结构成员是否会保留其定义中指定的顺序,答案是肯定的。该标准要求连续成员的地址不断增加:

第 6.7.2.1p13 节:

在一个 结构对象,非位域 成员及所在单位 位域驻留的地址 按它们的顺序增加 已声明。

packed 属性的文档明确指出只有填充/对齐受到影响:

packed 属性指定一个 变量或结构字段应该 有最小的可能 对齐——一个字节代表一个变量,和 一个字段,除非你 指定一个更大的值 对齐属性。

【讨论】:

如果位域 ordering 被维护,GCC 文档引用并没有让我清楚。【参考方案3】:

是的。

但是,使用__attribute__((__packed__)) 并不是做你正在做的事情的好方法。

它不能解决字节顺序问题 访问结构会很慢 尽管 gcc 的流行导致了其他编译​​器经常实现 gcc 扩展的情况,但使用此编译器特定的扩展意味着您没有符合要求的 C 程序。这意味着如果另一个编译器甚至未来的 gcc 更改了 packed 或根本没有实现它,那么你就很不走运,甚至不能向任何人抱怨。 Gcc 明天可能会放弃它,并且仍然是 C99 编译器。 (好吧,确切的事情不太可能。)我们大多数人尝试编写符合标准的程序并不是因为我们对使用供应商软件有某种抽象的敌意,或者想要一些代码纯度的学术标准,而是因为我们知道只有符合标准的语言特性才有精确且通用的规范,因此如果我们这样做,那么依靠我们的代码在日常和系统之间做正确的事情要容易得多。 您正在重新发明***;这个问题已经以符合标准的方式解决了:请参阅 YAMLXML、JSON。 如果它是 YAML、XML 和 JSON 不可用的低级协议,您真的应该采用单独的基本类型,应用您主机的 hton?() 和 ntoh?() 版本,然后 memcpy() 它们到输出缓冲区。我意识到直接从结构中读取和写入的传统由来已久,但后来当它从 32 位环境迁移到 64 位环境时,我也花了很长时间修复该代码......

【讨论】:

@DigitalRoss 我是第一个编写严格符合 ISO C90 的代码的人。我从不使用特定于编译器的技巧。但是,在这种情况下,我编写代码完全是为了个人使用。 好吧,我同意我的回答没有很好地针对你的问题,我想我会删除它...... @DigitalRoss 不要删除答案!它可能会帮助其他出现此线程的人。 如果 OP 只关心“个人使用”并且在链接的两边都有具有相同字节顺序的机器,那么几乎可以肯定两边也有相同的对齐约束。虽然对齐是实现定义的,但这并不意味着它会在每次编译器修订时改变。保持对齐行为对于除了最简单的库代码使用之外的所有内容都是必不可少的,并且 x86 上的 gcc 甚至排除了 double 的最佳对齐,以保持与现有对齐 ABI 的兼容性。【参考方案4】:

根据您要执行的操作,我强烈建议您也使用 stdint.h 中的固定大小数据类型(即 uint32_t、int16_t 等)。使用固定大小的数据类型将使您不必执行以下操作:

struct name

    short field : 8;
;

【讨论】:

【参考方案5】:

我们经常使用这种技术在字节数组和结构之间转换消息,并且从未遇到过问题。您可能必须自己执行字节顺序转换,但字段顺序不是问题。如果您对数据类型大小有任何顾虑,您可以随时指定字段大小,如下所示:

struct foo

  short someField : 16 __attribute__ ((packed));
;

这保证 someField 将被存储为 16 位,并且不会被重新排列或更改以适应字节边界。

【讨论】:

【参考方案6】:

是的,C 保证结构元素不会被重新排序。 (可能有扩展或花哨的优化系统可能会改变这一点,但默认情况下在 gcc 中不会。)

【讨论】:

C++ 允许使用介入的访问说明符对元素进行重新排序。这不会影响 C++ 的 C 子集,因为 C 结构根本没有任何访问说明符。

以上是关于GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?的主要内容,如果未能解决你的问题,请参考以下文章

等效于 Visual C++ 中 gcc 的 __attribute__ 格式

GCC 的 __attribute__((__packed__)) 是不是保留原始顺序?

GCC __attribute__((constructor)) 在对象构造函数之前调用

C 语言编程 — GCC Attribute 语法扩展

C 语言编程 — GCC Attribute 语法扩展

Linux gcc支持的语法 __attribute__ 属性设置