为什么此结构大小为3而不是2?


91

我已经定义了这个结构:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

sizeof(col)给我3的输出,但它不应该是2?如果仅评论一个元素,sizeof则为2。我不明白为什么:3个元素的5个元素等于15位,并且小于2个字节。

定义这样的结构时是否存在“内部尺寸”?我只需要澄清一下,因为从到目前为止的语言概念来看,我期望的大小为2字节,而不是3字节。


4
这可能是对齐的优化。如果下一个位大小不适合实际占用的空间,它将开始一个新字节。
2014年

4
除非您有一些外部约束要求位打包,并且平台为标准提供了其他保证,否则使用位域毫无意义。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2014年

3
请注意,对于C,使用char的可移植性不如使用int的可移植性stackoverflow.com/a/23987436/23118
Hlovdal 2014年

2
注意,几乎所有有关位字段的内容都是实现定义的。您可能会从不同的编译器获得不同的答案,并且没有追索权。还要注意,由于您没有指定signed charunsigned char,因此您不查看文档就无法确定编译器是将char位字段中的“纯文本” 视为带符号还是无符号,并且该决定(理论上)可能与关于是否将其视为决定的决定不同。char在位字段之外使用时,“ plain” 是带符号的或无符号的。
乔纳森·勒夫勒

3
具体而言,在C99,§6.7.2.1struct和union说明符,¶4 位字段应具有一种类型,是一个合格的或不合格的版本_Boolsigned intunsigned int,或一些其他实现定义类型。char因此, 使用属于“其他实现定义的类型”类别。
乔纳森·勒夫勒

Answers:


95

因为您使用的char是字段的基础类型,所以编译器尝试按字节对位进行分组,并且由于它不能在每个字节中放置超过8位,因此每个字节只能存储两个字段。

您的结构使用的位的总和为15,因此适合大量数据的理想大小为a short

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

上面的代码(用于像我的64位平台)确实会产生2第二个结构。对于大于a的任何内容short,该结构将填充不超过所使用类型的一个元素,因此-对于同一平台-该结构最终将以4表示大小int,以8表示大小long等。


1
建议的结构定义仍然是错误的。正确的结构定义将使用“ unsigned short”。
user3629249 2014年

21
@ user3629249为什么未签名的短字符“正确”?如果用户要存储从-4到3,则短路是正确的。如果用户要存储0到7,则无符号的short是正确的。最初的问题使用带符号的类型,但我无法确定这是有意还是无意。
2014年

2
为什么会有差别beetween charshort
GingerPlusPlus 2014年

5
@BruceDawson:该标准允许char未签名的实现……
Thomas

@ThomasEding是的,该标准确实允许对char进行无符号签名。但是我的主要观点仍然是,没有理由声称未签名的短单是正确的(尽管通常是正确的)。
布鲁斯·道森

78

因为您没有跨最小对齐边界(1个字节)的位数据包字段,所以它们很可能像

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(同一字节中字段/填充的顺序不是有意的,这只是为了给您一个想法,因为编译器可以将它们放下它喜欢的方式)


16

前两个位字段适合单个char。第三者不能适应这一点char,需要一个新的。3 + 3 + 3 = 9,这不适合8位字符。

因此,第一对取一个char,第二对取一个char,最后一位取第三char


15

大多数编译器都允许您控制填充,例如使用#pragmas。这是GCC 4.8.1的示例:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

请注意,存在编译器的默认行为是有原因的,它可能会为您提供更好的性能。


9

尽管ANSI C标准对如何打包位域的规定太少了,与“允许编译器打包但认为合适的位域”相比,提供任何显着的优势,但是在许多情况下,它还是禁止编译器以最有效的方式打包内容。

特别地,如果结构包含位域,则要求编译器将其存储为包含某个或多个“正常”存储类型的匿名字段的结构,然后在逻辑上将每个此类字段细分为其组成的位字段部分。因此,给出:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

如果unsigned char为8位,则要求编译器分配该类型的四个字段,并为除一个字段之外的所有字段分配两个位字段(它们将位于char其自己的字段中)。如果所有char声明都已替换为short,那么将有两个类型的字段short,其中一个字段将包含五个位域,而另一个字段将保留其余两个位域。

在没有对齐限制的处理器上,可以通过使用unsigned short前五个字段和unsigned char后两个字段(三个字节存储七个三位字段)来更有效地布局数据。尽管应该可以在三个字节中存储八个三位字段,但是编译器只能允许存在一个可用作“外部字段”类型的三字节数字类型。

我个人认为定义的位域基本上是无用的。如果代码需要处理二进制打包的数据,则应明确定义实际类型的存储位置,然后使用宏或其他某种方式访问​​其位。如果C支持以下语法,则将很有帮助:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

这样的语法(如果允许)将使代码能够以可移植的方式使用位域,而无需考虑字长或字节顺序(foo0将位于f1的三个最低有效位,但是可以将它们存储在较低或较高的地址)。但是,缺少这种功能,宏可能是处理此类问题的唯一可移植方式。


2
不同的编译器将对位字段进行不同的布局。我写了一些有关Visual C ++如何做到这一点的文档。它指出了一些令人讨厌的陷阱: randomascii.wordpress.com/2010/06/06/…–
Bruce Dawson

好吧,您说的是一种普通类型的存储,并使用位字段运算符来完成所需的单个变量并使用一些宏来简化此机制。我认为在c / c ++中生成的代码也做了类似的事情。使用结构只是为了更好地组织代码,实际上根本没有必要。
拉斐尔2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.