为什么打包结构的大小在这里是 5 而不是 4 字节?

Why is the size of packed struct 5 instead of 4 bytes here?

参见在线示例: 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 位。

这是因为内存对齐:编译器不会在一个字节的中间开始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。显然,该特定更改可能不是您想要的;我只是在证明问题是开始一个不在字节边界上的新字段。


* "probably" 因为最终取决于编译器。

在某些编译器中,对于 "merge" 位,所有项都必须属于同一类型。因此,在您现在拥有 uint8_t 的位置将其设置为 uint32_t - 在 IdeOne 使用的编译器中似乎并非如此'

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

正如其他人所指出的,您不能在字节边界以外的地方开始新结构。我通过在联合中添加第二个结构来修复它,如下所示: 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;
}

问题是 __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 的结构的联合体。换句话说,您将需要进行一些重复或求助于访问器宏或成员函数。

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

  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 个字节。