【问题标题】:Why is the size of packed struct 5 instead of 4 bytes here?为什么这里打包结构的大小是 5 而不是 4 字节?
【发布时间】:2015-06-05 18:07:53
【问题描述】:

查看在线示例: Ideone example

struct {
  union {
    struct {
      uint32_t messageID : 26;
      uint8_t priority : 3; 
    } __attribute__ ((packed));
    uint32_t rawID : 29;
  } __attribute__ ((packed));
  uint8_t canFlags : 3;
} __attribute__ ((packed)) idSpecial;

为什么编译器会将结构体的大小报告为 5 个字节而不是 4 个字节?它应该包含 32 位。

【问题讨论】:

  • @DavidTitarenco:我不认为那个特定的帖子对位域有好处。如果这个问题已经在其他地方得到回答,我不会感到惊讶。
  • 响应者应该注意OP使用non-standard __attribute__((packed)) GCC扩展来避免填充。因此,说允许编译器插入填充是不正确的——当使用__attribute__((packed)) 时,GCC 将组织结构以避免填充(在不支持未对齐读取的平台上使访问成本更高)。
  • 简答:结构和联合必须是一个字节的倍数,甚至是匿名的。

标签: c++ c gcc struct bit-fields


【解决方案1】:

问题是__attribute__((packed)) 不执行按位打包。它只是保证struct 成员之间没有填充。您可以尝试这个更简单的示例,其中大小也报告为 5:

typedef struct structTag {
    struct {
      uint32_t messageID : 26;
      uint8_t priority : 3;
    } __attribute__ ((packed));
    uint8_t canFlags : 3;
} __attribute__ ((packed)) idSpecial;

按位打包仅适用于位域成员。您需要将结构重新设计为具有位域 messageID/priority/canFlags 的结构和具有位域 rowID/canFlags 的结构的联合。换句话说,您将需要有一些重复或求助于访问宏或成员函数。

【讨论】:

    【解决方案2】:

    这是因为内存对齐:编译器不会在一个字节的中间开始canFlags,它会在下一个字节的开头(可能是*)开始它。所以你的初始联合有四个字节,canFlags 有一个字节。

    例如,如果您将 canFlags 移动到联合中,则它(可能*)的大小为 4:

    typedef struct structTag {
      union {
        struct {
          uint32_t messageID : 26; /* 26bit message id, 67108864 ids */
          uint8_t priority : 3; /* priority: MUST BE 0 */
        } __attribute__ ((packed));
        uint32_t rawID : 29;
        uint8_t canFlags : 3; /* <==== Moved */
      } __attribute__ ((packed));
    } __attribute__ ((packed)) idSpecial;
    

    Updated example on ideone。显然,那个特定的改变可能不是你想要的。我只是在证明问题是开始一个不在字节边界上的新字段。


    * “可能”,因为最终取决于编译器。

    【讨论】:

    • 在 rawid 旁边的 canFlags 使它们联合 - 我认为这不是 OP 想要的。
    • @MatsPetersson:是的,OP 必须重新设计,我只是指出为什么他们现在的设计是 5 而不是 4。
    • 根据您的建议,我们将rawIDcanFlags 包装在一个结构中,并在联合内第一个结构的末尾添加了3 位的虚拟填充,现在整个结构有4 个字节大正如预期的那样。
    【解决方案3】:

    使用数据结构对齐在计算机内存中排列和访问数据。其中有两个相关的问题

    1. 对齐
    2. 填充

    当计算机执行写操作时,它通常以 4 字节的倍数写入(对于 32 位系统)。这一行为的一个原因是提高绩效的目标。因此,当您编写任何具有前 1 字节变量和后 4 字节变量数据的数据结构时,它将在前 1 字节数据之后进行填充,以使其在 32 位边界上对齐。

    struct {
      union {
        struct {
          uint32_t messageID : 26;
          uint8_t priority : 3; 
        } __attribute__ ((packed));
        uint32_t rawID : 29;
      } __attribute__ ((packed));
      uint8_t canFlags : 3;
    } __attribute__ ((packed)) idSpecial;
    

    现在在上述数据结构中,您使用的是__attribute__ ((packed)),这意味着没有填充。所以 uint32_t 是 4 个字节,但是你说它有 26 位和 3 位的优先级。现在,由于您在一个结构中拥有两个变量,因此它将保留 32 位而不是 29 位,以便您的第一个结构的信息在边界上对齐。

    现在对于 canFlags 它将需要另一个字节。这样就变成了 5 个字节而不是 4 个。

    【讨论】:

      【解决方案4】:

      在某些编译器中,要“合并”位,所有项必须属于同一类型。所以在你现在有uint8_t 的地方设置为uint32_t - 在IdeOne 使用的编译器中似乎不是这种情况'

      [无论如何,如何合并位仍然取决于编译器,因此绝对保证您的数据存储为 32 位的唯一方法是使用单个 uint32_t 并声明一个类是否进行了相关的移位和与/或运算来操纵该值-唯一的保证是结构中的一个元素将至少具有您要求的位数]

      正如其他人指出的那样,您不能在字节边界以外的地方开始新结构。我通过在联合中添加第二个结构来修复它,如下所示: http://ideone.com/Mr1gjD

      #include <stdint.h>
      #include <stdio.h>
      
      typedef struct structTag {
        union {
          struct {
            uint32_t messageID : 26; /* 26bit message id, 67108864 ids */
            uint8_t priority : 3; /* priority: MUST BE 0 */
          } __attribute__ ((packed));
          struct {
            uint32_t rawID : 29;
            uint8_t canFlags : 3;
          };
        } __attribute__ ((packed));
      } __attribute__ ((packed)) idSpecial;
      
      int main() {
          printf("size: %d", sizeof(idSpecial));
          return 0;
      }
      

      【讨论】:

      • @T.J.Crowder:同意。尽管我已经看到出于某种原因这很重要的案例 - 我认为可能是 MS 编译器有这个问题。
      猜你喜欢
      • 2021-05-19
      • 2014-12-29
      • 1970-01-01
      • 2019-09-16
      • 2015-12-24
      • 2023-03-28
      • 2021-12-18
      • 2019-02-03
      • 1970-01-01
      相关资源
      最近更新 更多