为什么打包结构的大小在这里是 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 的结构的联合体。换句话说,您将需要进行一些重复或求助于访问器宏或成员函数。
使用数据结构对齐在计算机内存中排列和访问数据。其中有两个相关问题
- 对齐
- 填充
计算机执行写操作时,通常以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 个字节。
参见在线示例: 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 的结构的联合体。换句话说,您将需要进行一些重复或求助于访问器宏或成员函数。
使用数据结构对齐在计算机内存中排列和访问数据。其中有两个相关问题
- 对齐
- 填充
计算机执行写操作时,通常以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 个字节。