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__))
并不是做你正在做的事情的好方法。
【讨论】:
@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__)) 是不是保留原始顺序?