2014-11-02 124 views
90

我定义这个结构:为什么这个结构大小3而不是2?

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位的五个元素等于15位,并且小于2个字节。

定义像这样的结构是否有“内部大小”?我只需要澄清,因为从我迄今为止的语言概念来看,我预计的是2字节的大小,而不是3.

+4

这可能是对齐的优化。如果下一个比特大小不适合实际占用的空间,它将启动一个新的字节。 – 2014-11-02 13:52:42

+4

除非您有一些外部约束需要位打包,并且您的平台为标准提供了一些额外的保证,否则使用位域几乎没有意义。 – 2014-11-02 15:35:36

+3

请注意,对于C来说,使用char不如使用int可移植,http://stackoverflow.com/a/23987436/23118。 – hlovdal 2014-11-02 15:41:52

回答

94

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

您的结构使用的位的总数是15,所以适合这么多数据的理想大小将是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对于第二结构体。对于任何超过short更大,该结构将填补使用类型的不超过一个元素,所以 - 对于同一个平台 - 该结构将结束与大小四为int,八项long

+1

建议的结构定义仍然是错误的。正确的结构定义将使用'unsigned short'。 – user3629249 2014-11-02 21:21:25

+21

@ user3629249为什么无符号短'正确'?如果用户想要从-4存储到3,那么短小是正确的。如果用户想要存储从0到7,那么无符号短小是正确的。原始问题使用签名类型,但我不能说这是故意的还是意外的。 – 2014-11-03 07:14:46

+2

为什么'char'和'short'有什么区别? – GingerPlusPlus 2014-11-03 13:22:51

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

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,可以更有效地布置数据,以三个字节存储七个三位字段。虽然应该可以将三个字节的8位三位字段存储起来,但编译器只允许在存在可以用作“外部字段”类型的三字节数字类型的情况下。

就个人而言,我认为位域定义为基本无用。如果代码需要使用二进制打包数据,它应该明确定义实际类型的存储位置,然后使用宏或其他一些手段访问其中的位。这将是有益如果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++如何与它相关的文档。它指出了一些令人讨厌的陷阱: https://randomascii.wordpress.com/2010/06/06/bit-field-packing-with-visual-c/ – 2014-11-03 07:15:55

+0

那么你说的是相当于一家商店正常类型并使用位域操作符来完成单个感兴趣的变量,并简化这种机制使用一些宏。我认为在c/C++中生成的代码也是这样做的。使用结构只是为了“更好”的代码组织,实际上并不是必要的。 – Raffaello 2014-11-03 15:31:53

15

大多数编译器允许你控制填充,e.g. using #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 
} 

注意,编译器的默认行为是有原因的,将可能给你更好的性能。

相关问题