本文介绍了char和int上的C struct位域之间的差异的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在C语言中使用位域时,我发现我所期望的差异与用于声明字段的实际类型无关.

我没有找到任何明确的解释.现在,可以确定问题所在,因此,即使没有明确的答复,该帖子对遇到相同问题的任何人也可能很有用.即使有人可以指出正式的解释,这种说法也很棒.

以下结构占用2个字节的内存.

  struct {char field0:1;//1位-位0char field1:2;//2位-位2降至1字符字段2;//8位-15位下降到8位} reg0; 

这一个占用4个字节的内存,问题是为什么?

  struct {int field0:1;//1位-位0int field1:2;//2位-位2降至1字符字段2;//8位-15位下降到8位} reg1; 

在两种情况下,位以相同的方式组织在内存中:字段2始终将位15减小到8.

我试图找到关于这个主题的文献,但是仍然无法获得明确的解释.

我可以找到两个最合适的链接:

但是,没有人能真正解释第二个结构为何占用4个字节的原因.实际上,仔细阅读后,我什至希望该结构占用2个字节.

在两种情况下

  • field0占用1位
  • field1占用2位
  • field2占用8位,并与第一个可用字节地址对齐

因此,两种情况下有用数据都需要2个字节.

那么,使reg1占用4个字节的幕后原因是什么?

完整代码示例:

  #include"stdio.h"//使用char注册结构typedef struct {//Reg0struct _reg0_bitfieldsA {char field0:1;char field1:2;字符字段2;} reg0;//Nextregchar NextReg;} regfileA_t;//使用int注册结构typedef struct {//Reg1struct _reg1_bitfieldsB {int field0:1;int field1:2;字符字段2;} reg1;//Regchar NextReg;} regfileB_t;regfileA_t regsA;regfileB_t regsB;int main(int argc,char const * argv []){int * ptrA,* ptrB;printf("sizeof(regsA)==%-0d \ n",sizeof(regsA));//打印3-如预期printf("sizeof(regsB)==%-0d \ n",sizeof(regsB));//打印8-为什么?printf("\ n");printf("sizeof(regsA.reg0)==%-0d \ n",sizeof(regsA.reg0));//打印2-如预期printf("sizeof(regsB.reg0)==%-0d \ n",sizeof(regsB.reg1));//打印4-int位字段告诉结构然后使用4个字节.printf("\ n");printf("addrof(regsA.reg0)== 0x%08x \ n",(int)(& regsA.reg0));//0x0804A028printf("addrof(regsA.reg1)== 0x%08x \ n",(int)(®sA.NextReg));//0x0804A02A =上一页+ 2printf("addrof(regsB.reg0)== 0x%08x \ n",(int)(& regsB.reg1));//0x0804A020printf("addrof(regsB.reg1)== 0x%08x \ n",(int)(& regsB.NextReg));//0x0804A024 =上一页+ 4-那时我的寄存器不在严格的位置.printf("\ n");regsA.reg0.field0 = 1;regsA.reg0.field1 = 3;regsA.reg0.field2 = 0xAB;regsB.reg1.field0 = 1;regsB.reg1.field1 = 3;regsB.reg1.field2 = 0xAB;ptrA =(int *)®sA;ptrB =(int *)®sB;printf("regsA.reg0.value == 0x%08x \ n",(int)(* ptrA));//0x0000AB07(预期)printf("regsB.reg0.value == 0x%08x \ n",(int)(* ptrB));//0x0000AB07(预期)返回0;} 

当我第一次编写该结构时,我希望获得reg1仅占用2个字节,因此下一个寄存器位于offset = 2处.

解决方案

该标准的相关部分为 C11/C17 6.7.2.1p11 :

C11/C17 6.7.2.1有关p5

,并且您正在使用 char ,这意味着没有任何一般性讨论-对于特定的实现,请查看编译器手册.这是GCC的.

从这2个摘录中可以得出,一个实现可以自由使用任何想要的类型来实现位域-它甚至可以对这两个都使用 int64_t 大小为16字节的结构的情况.符合标准的实现必须要做的是,如果剩余足够的空间,则将位打包在选定的可寻址存储单元中.


对于,以下代表:

即在System-V ABI中,386, int f:1 表示位域 f 必须在 int .如果剩余整个字节空间,则在同一struct中的以下 char 将被包装在此 int 中,即使它不是一点点-字段.

利用这些知识,布局

  struct {整数a:1;//1位-位0整数b:2;//2位-位2降至1字符c;//8位-15位下降到8位} reg1; 

将是

  1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| a b b xx x xx | c cc c cc c c | x xx xx xx | x xx xx xx x |< ------------------------------ int ----------------------------> 

以及

的布局

  struct {字符a:1;//1位-位0字符b:2;//2位-位2降至1字符c;//8位-15位下降到8位} reg1; 

将是

  10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5| a b b xx x x x | c c c c c c c c c |< ----字符---->< ----字符----> 

因此,存在一些棘手的情况.在这里比较2个定义:

  struct x {短a:2;短b:15;字符c;};结构y {整数a:2;整数b:15;字符c;}; 

因为位域一定不能越过单元边界,所以 struct x 成员 a b 需要分别使用不同的短裤.然后没有足够的空间容纳 char c ,因此它必须在此之后.并且整个结构必须与 short 适当对齐,以便在i386上为6个字节.但是,后者会将 a b 封装在 int 的最低17位中,并且由于其中还剩下一个完整的可寻址字节 int c 也将打包在这里,因此 sizeof(结构y)将为 4 .


最后,您必须真正指定 int char 是否已签名-默认值可能不是您所期望的!Standard将其留给实现,而GCC拥有一个编译时开关来更改它们.

When using bitfields in C, I found out differences I did not expect related to the actual type that is used to declare the fields.

I didn't find any clear explanation. Now, the problem is identified, so if though there is no clear response, this post may be useful to anyone facing the same issue.Still if some can point to a formal explanation, this coudl be great.

The following structure, takes 2 bytes in memory.

struct {
  char field0 : 1; // 1 bit  - bit 0
  char field1 : 2; // 2 bits - bits 2 down to 1
  char field2 ;    // 8 bits - bits 15 down to 8
} reg0;

This one takes 4 bytes in memory, the question is why ?

struct {
  int  field0 : 1; // 1 bit  - bit 0
  int  field1 : 2; // 2 bits - bits 2 down to 1
  char field2 ;    // 8 bits - bits 15 down to 8
} reg1;

In both cases, the bits are organized in memory in the same way: field 2 is always taking bits 15 down to 8.

I tried to find some literarure on the subject, but still can't get a clear explanation.

The two most appropriate links I can found are:

However, none really explains really why the second structure is taking 4 bytes. Actually reading carefully, I would even expect the structure to take 2 bytes.

In both cases,

  • field0 takes 1 bit
  • field1 takes 2 bits
  • field2 takes 8 bits, and is aligned on the first available byte address

Hence, the useful data requires 2 bytes in both cases.

So what is behind the scene that makes reg1 to take 4 bytes ?

Full Code Example:

#include "stdio.h"
// Register Structure using char
typedef struct {
    // Reg0
    struct _reg0_bitfieldsA {
      char field0 : 1;
      char field1 : 2;
      char field2 ;
    } reg0;

    // Nextreg
    char NextReg;

} regfileA_t;

// Register Structure using int
typedef struct {
    // Reg1
    struct  _reg1_bitfieldsB {
      int field0 : 1;
      int field1 : 2;
      char field2 ;
    } reg1;

    // Reg
    char NextReg;
} regfileB_t;


regfileA_t regsA;
regfileB_t regsB;


int main(int argc, char const *argv[])
{
    int* ptrA, *ptrB;

    printf("sizeof(regsA) == %-0d\n",sizeof(regsA));   // prints 3 - as expected
    printf("sizeof(regsB) == %-0d\n",sizeof(regsB));   // prints 8 - why ?
    printf("\n");
    printf("sizeof(regsA.reg0) == %-0d\n",sizeof(regsA.reg0)); // prints 2 - as epxected
    printf("sizeof(regsB.reg0) == %-0d\n",sizeof(regsB.reg1)); // prints 4 - int bit fields tells the struct to use 4 bytes then.
    printf("\n");
    printf("addrof(regsA.reg0) == 0x%08x\n",(int)(&regsA.reg0));     // 0x0804A028
    printf("addrof(regsA.reg1) == 0x%08x\n",(int)(&regsA.NextReg));  // 0x0804A02A = prev + 2
    printf("addrof(regsB.reg0) == 0x%08x\n",(int)(&regsB.reg1));     // 0x0804A020
    printf("addrof(regsB.reg1) == 0x%08x\n",(int)(&regsB.NextReg));  // 0x0804A024 = prev + 4 - my register is not at the righ place then.
    printf("\n");

    regsA.reg0.field0 = 1;
    regsA.reg0.field1 = 3;
    regsA.reg0.field2 = 0xAB;

    regsB.reg1.field0 = 1;
    regsB.reg1.field1 = 3;
    regsB.reg1.field2 = 0xAB;

    ptrA = (int*)&regsA;
    ptrB = (int*)&regsB;
    printf("regsA.reg0.value == 0x%08x\n",(int)(*ptrA)); // 0x0000AB07 (expected)
    printf("regsB.reg0.value == 0x%08x\n",(int)(*ptrB)); // 0x0000AB07 (expected)

    return 0;
}

When I first write the struct I expected to get reg1 to take only 2 bytes, hence the next register was at the offset = 2.

解决方案

The relevant part of the standard is C11/C17 6.7.2.1p11:

that, in connection with C11/C17 6.7.2.1p5

and that you're using char means that there is nothing to discuss in general - for a specific implementation check the compiler manuals. Here's the one for GCC.

From the 2 excerpts it follows that an implementation is free to use absolutely whatever types it wants to to implement the bitfields - it could even use int64_t for both of these cases having the structure of size 16 bytes. The only thing a conforming implementation must do is to pack the bits within the chosen addressable storage unit if enough space remains.


For GCC on System-V ABI on 386-compatible (32-bit processors), the following stands:

i.e. in System-V ABI, 386, int f: 1 says that the bit-field f must be within an int. If entire bytes of space remains, a following char within the same struct will be packed inside this int, even if it is not a bit-field.

Using this knowledge, the layout for

struct {
  int  a : 1; // 1 bit  - bit 0
  int  b : 2; // 2 bits - bits 2 down to 1
  char c ;    // 8 bits - bits 15 down to 8
} reg1;

will be

                     1                     2                 3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|a b b x x x x x|c c c c c c c c|x x x x x x x x|x x x x x x x x|

<------------------------------ int ---------------------------->

and the layout for

struct {
  char  a : 1; // 1 bit  - bit 0
  char b : 2; // 2 bits - bits 2 down to 1
  char c ;    // 8 bits - bits 15 down to 8
} reg1;

will be

                    1
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|a b b x x x x x|c c c c c c c c|

<---- char ----><---- char ---->

So there are tricky edge cases. Compare the 2 definitions here:

struct x {
    short a : 2;
    short b : 15;
    char  c ;
};

struct y {
    int a : 2;
    int b : 15;
    char  c ;
};

Because the bit-field must not cross the unit boundary, the struct x members a and b need to go to different shorts. Then there is not enough space to accommodate the char c, so it must come after that. And the entire struct must be suitably aligned for short so it will be 6 bytes on i386. The latter however, will pack a and b in the 17 lowest bits of the int, and since there is still one entire addressable byte left within the int, the c will be packed here too, and hence sizeof (struct y) will be 4.


Finally, you must really specify whether the int or char is signed or not - the default might be not what you expect! Standard leaves it up to the implementation, and GCC has a compile-time switch to change them.

这篇关于char和int上的C struct位域之间的差异的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-15 18:13