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

时间:2015-04-01 07:15:05

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

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

4 个答案:

答案 0 :(得分:6)

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

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

按位打包仅适用于位域成员。您需要将结构重新设计为结构与bitfields messageID / priority / canFlags的结合,以及带有bitfields rowID / canFlags的结构。换句话说,您需要复制或使用访问器宏或成员函数。

答案 1 :(得分:5)

由于内存对齐:编译器不能在一个字节的中间中启动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。显然,这种具体的改变可能并不是你想要的;我只是证明问题是开始一个不在字节边界上的新字段。


*&#34;可能&#34;因为最终它取决于编译器。

答案 2 :(得分:1)

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

  1. 对齐
  2. 填充
  3. 当计算机执行写操作时,它通常以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个。

答案 3 :(得分:0)

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

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

正如其他人所指出的那样,除了字节边界之外,你无法启动新结构。我通过在union中包含第二个结构来修复它,如下所示: 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;
}