GCC的__attribute __((__ packed__))是否保留原始排序?

时间:2009-11-18 15:32:04

标签: c gcc compiler-construction networking struct

目的

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

问题

除了保证struct X尽可能小之外,gcc是否保证用__attribute __((__ packed__)定义的struct保留原始排序?我已经做了相当多的搜索,但我还没有找到关于这种保证是否存在的任何文件。

备注

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

6 个答案:

答案 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__))并不是一个很好的方法来做你正在做的事情。

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

答案 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元素。 (可能有扩展或花哨的优化系统可能会改变这种情况,但默认情况下不会改变它。)