如何确定/测量具有位域的结构体的大小?

43
#include <stdio.h>

typedef struct size
{
        unsigned int a:1;
        unsigned int b:31;
        unsigned int c:1;
} mystruct;

int main()
{
        mystruct a;
        printf("%d", sizeof(a));
        return 0;
}
  • 使用int b:31时,输出结果为8。
  • 使用int b:1时,输出结果为4。
  • 使用int b:32时,输出结果为12。

有人可以解释一下原因吗?


5
如果您曾经想知道64位机器何时使用的空间比32位机器少(或者至少不比32位机器多),那么这就是一个例子。 - Jonathan Leffler
@JonathanLeffler:你确定吗?在x64 Linux/ELF上,GCC显示(1,32,1)的大小为12。我相信影响这种结构大小的唯一因素是底层类型的大小,而在这种情况下,底层类型是“unsigned int”。 - Dietrich Epp
@DietrichEpp:ISO/IEC 9899:2011 §6.7.2.1 结构体和联合体说明符:指定位域宽度的表达式应为具有非负值的整数常量表达式,其值不超过省略冒号和表达式后将要指定的对象类型的宽度。我认为这支持了你的观点...但如果我们找到一个ILP64机器(而不是通常的LP64机器),那么我会绕过它。 - Jonathan Leffler
4个回答

61

顺序很重要。以下代码将输出:8

#include<stdio.h>

typedef struct size
{
        unsigned int b:32;
        unsigned int a:1;
        unsigned int c:1;
}mystruct;

int main(int argc, char const *argv[])
{
        mystruct a;
        printf("\n %lu \n",sizeof(a));
        return 0;
}

无符号整数是一个32位的整数,占用4个字节。内存在内存中连续分配。


Option 2:

无符号整数是一个32位的整数,占用4个字节。内存是在内存中连续分配的。

unsigned int a:1;       // First 4 bytes are allocated
unsigned int b:31;      // Will get accomodated in the First 4 bytes
unsigned int c:1;       // Second 4 bytes are allocated

输出:8


选项2:

unsigned int a:1;       // First 4 bytes are allocated
unsigned int b:32;      // Will NOT get accomodated in the First 4 bytes, Second 4 bytes are allocated
unsigned int c:1;       // Will NOT get accomodated in the Second 4 bytes, Third 4 bytes are allocated

输出:12


选项3:

unsigned int a:1;       // First 4 bytes are allocated
unsigned int b:1;       // Will get accomodated in the First 4 bytes
unsigned int c:1;       // Will get accomodated in the First 4 bytes

输出:4


选项 4:

unsigned int b:32;      // First 4 bytes are allocated
unsigned int a:1;       // Second 4 bytes are allocated
unsigned int c:1;       // Will get accomodated in the Second 4 bytes

输出:8


7
在我看来,这应该被接受为答案。 - zephyr
位域的位置决定了其大小,因为它会引起填充。就像建议将结构体中最大的元素放在顶部以获得最佳/最小填充一样。 - ashish
没错,这应该是被接受的答案。虽然MdT在三年后才发布了它。无论如何——从这个评论中学到了很多东西。我不知道很多我从这篇文章中学到的东西。 - todovvox
1
@zephyr 在我看来,这应该是被接受的答案。 不,这个答案是错误的。评论中的Will NOT get accomodated in the First 4 bytes, Second 4 bytes are allocated不正确的。根据C标准:“如果剩余空间不足,则未适合的位域将被放入下一个单元或重叠相邻单元是由实现定义的。” - Andrew Henle

31

我不知道您是否了解位域(bitfields)的概念,但是我假设您已经了解。

显然,在您的实现中,unsigned int是32位整数,占用4个字节。这解释了前两个示例。三个共计33位的位域显然无法放入一个unsigned int中,因此第一个示例需要8个字节。共计3位的三个位域可以放入一个unsigned int中,因此第二个示例只需要4个字节。

此外,位域不能跨越多个整数,这解释了第三个示例的情况。我不记得这是标准的要求还是您的实现细节。无论哪种情况,由于b是32位,它本身就占用了一个完整的unsigned int,强制ac都要在中间整数之前和之后各自占用一个unsigned int。因此需要12个字节。


12
根据Steve jessop的回答,为了补充他的回答,这里提供一些可能有帮助的文档。
一个结构体或联合体的成员可以是除了可变长度类型之外的任何完整对象类型。此外,成员可以被声明为由指定数量的位组成 (包括符号位,如果有的话)。这样的成员称为位域,其宽度前面加上冒号。
实现可以分配任何足够大的可寻址存储单元来容纳一个位域。如果剩余空间足够,一个位域紧随结构中的另一个位域之后,应被打包成同一单元的相邻位。 如果空间不足,一个不能放置的位域是被放在下一个存储单元还是与相邻的单元重叠是由实现定义的。在一个单元内分配位域的顺序 (高位到低位或低位到高位) 是由实现定义的。可寻址存储单元的对齐方式是未指定的。
在结构体对象内,非位域成员和位域所在的单元的地址按照它们被声明的顺序递增。指向结构体对象的指针可以被适当地转换,并指向其初始成员 (如果该成员是位域,则指向它所在的单元),反之亦然。在结构体对象中可能有未命名的填充,但不会出现在其开头。
——ISO/IEC 9899:201x 6.7.2.1

需要特别注意的是,位域的声明类型并不指定存储其值的可寻址存储单元的大小。声明类型确实限制了允许的位域宽度,并且位域宽度与可寻址存储单元大小相互作用,但在布局方面有很大的变化余地。 - John Bollinger

10

对齐

编译器将结构的大小舍入为32位,每个对象的大小可能尝试引用32位,并同时保留您的位字段顺序。

因此,如果您在中间有一个32位项目,并且两侧有1位项目,则需要分配3个32位字:12个字节。

对于另外两种情况,这只是一个问题,即您的位字段序列可以打包到多少个32位对象中,同时仍保留字段顺序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接