2015-04-01 45 views
8

看到网上例如: Ideone example为什么在这里打包结构5而不是4字节的大小?

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位。

+0

可能重复[C中的结构和联合,确定大小和访问成员](http://stackoverflow.com/questions/3380118/structures-and-unions-in-c -determining-size-and-access-members) – 2015-04-01 07:19:41

+0

@DavidTitarenco:我不认为这个帖子对位域有好处。如果这个问题已经在其他地方得到了回答,那么我不会感到惊讶。 – 2015-04-01 07:22:17

+4

响应者应该注意,OP使用[非标准'__attribute__((packed))'](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html)GCC扩展来避免填充。所以,说编译器被允许插入填充是不正确的 - 当使用'__attribute __((packed))'时,GCC将组织结构以避免填充(在不支持未对齐的读取的平台上使访问更加昂贵)。 – user4815162342 2015-04-01 07:31:27

回答

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。显然,这种具体的变化可能不是你想要的;我只是证明问题是在字节边界上开始一个新字段。


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

+0

rawid旁边的canFlags使它们结合 - 我想这不是OP想要的。 – 2015-04-01 07:32:00

+0

@MatsPetersson:对,OP将不得不重新设计,我只是指出为什么他们为他们当前的设计获得5而不是4。 – 2015-04-01 07:32:53

+1

根据您的建议,我们将'rawID'和'canFlags'封装在一个结构中,并在联合内部的第一个结构的末尾添加了3位虚拟填充,现在整个结构的大小与预期的一样大4个字节。 – 2015-04-01 07:47:32

0

在一些编译器中,要“合并”这些位,所有项目必须是相同的类型。所以使它uint32_t你现在有uint8_t - 这似乎不是在编译器的情况下IdeOne使用'

[不管怎么样,它仍然由编译器如何合并位,所以它是唯一的以绝对保证你的数据存储为32位的方法是使用一个单独的uint32_t并声明一个类来执行相关的移位操作来控制这个值 - 唯一的保证是你的结构中的ONE元素会有至少与你所要求的位数一样多]

正如其他人指出的那样,除了字节边界之外,你不能启动一个新的结构。我固定它由具有工会内部的第二结构体,这样的: 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; 
} 
+0

@ T.J.Crowder:同意。尽管我曾经遇到过这种情况,因为某些原因 - 我认为这可能是MS编译器遇到的问题。 – 2015-04-01 07:42:39

6

的问题是,__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; 

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

1

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

  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.

相关问题