目的
我正在用C编写一个网络程序(特别是gnu89
),我想通过将某个struct X
重新解释为大字节数组(又名char
)来简化事情,发送网络上的字节数,并在另一侧将它们重新解释为struct X
。为此我决定使用gcc的__attribute __((__ packed__))。我已尽力确保正确完成此操作(即我已考虑到字节序和其他相关问题)。
问题
除了保证struct X
尽可能小之外,gcc是否保证用__attribute __((__ packed__)定义的struct
保留原始排序?我已经做了相当多的搜索,但我还没有找到关于这种保证是否存在的任何文件。
备注
可以安全地假设发送方和接收方都不会遇到可移植性问题(例如服务器上的sizeof(int)
等于客户端上的sizeof(int)
。
答案 0 :(得分:39)
是的,__attribute__((packed))
(不需要第二组下划线)是实现二进制(即非文本)网络协议的正确方法。元素之间不会有间隙。
但是你应该明白packed
不仅打包了结构,还包括:
但是,如果直接访问struct成员,编译器将只处理错位。您应该永远不会指向打包结构的成员(除非您知道成员的必需对齐为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
答案 1 :(得分:27)
假设您在询问结构成员是否会保留其定义中指定的顺序,答案是肯定的。标准要求后续成员的地址不断增加:
第§6.7.2.1p13:
在一个 结构对象,非位域 成员和单位 位字段驻留具有地址 增加他们的顺序 宣布。
并且packed属性的文档明确指出只有填充/对齐受影响:
packed属性指定a 变量或结构域应该 尽可能小 alignment - 变量的一个字节,和 一个字段,除非你 用。指定一个更大的值 对齐属性。
答案 2 :(得分:3)
但是,使用__attribute__((__packed__))
并不是一个很好的方法来做你正在做的事情。
答案 3 :(得分:3)
根据您要做的事情,我强烈建议您也使用stdint.h中的固定大小的数据类型(即uint32_t,int16_t等)。使用固定大小的数据类型将阻止您执行以下操作:
struct name
{
short field : 8;
};
答案 4 :(得分:2)
我们经常使用这种技术在字节数组和结构之间转换消息,并且从未遇到过它的问题。您可能必须自己执行字节顺序转换,但字段顺序不是问题。如果您对数据类型大小有任何疑虑,可以随时指定字段大小:
struct foo
{
short someField : 16 __attribute__ ((packed));
};
这可以保证someField将存储为16位,不会重新排列或更改以适应字节边界。
答案 5 :(得分:1)
是的,C保证不会重新排序struct元素。 (可能有扩展或花哨的优化系统可能会改变这种情况,但默认情况下不会改变它。)