繁体   English   中英

char和int上的C struct位域之间的差异

[英]different between C struct bitfields on char and on int

当在C中使用位域时,我发现与用于声明字段的实际类型无关的差异。

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

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

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;

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

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;

在这两种情况下,位的存储方式都相同:字段2始终将位15减小到8。

我试图找到有关该主题的文献,但仍然无法获得明确的解释。

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

但是,没有人真正解释第二个结构为什么要占用4个字节。 实际上,仔细阅读后,我什至希望结构占用2个字节。

在这两种情况下

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

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

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

完整代码示例:

#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;
}

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

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

  1. 一个实现可以分配任何足够大的可寻址存储单元来容纳一个位字段 如果有足够的空间,则应将紧随结构中另一个位域之后的位域打包到同一单元的相邻位中。 如果剩余空间不足,则将实现不适当的位字段放入下一个单元还是与相邻单元重叠。 单位内的位域分配顺序(从高位到低位或从低位到高位)由实现定义。 未指定可寻址存储单元的对齐方式。

C11 / C17 6.7.2.1p5有关

  1. 位域的类型必须是_Bool的合格或不合格版本,signed int,unsigned int或其他一些实现定义的类型 是否允许原子类型由实现定义。

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

从2个摘录中可以得出,一个实现完全可以自由使用它想要实现位域的任何类型,对于这两种情况,它甚至可以使用int64_t来构造16字节大小的结构。 一个符合标准的实现必须做的唯一的事情是,如果有足够的空间仍然是选择的可寻址存储单元内收拾位。


对于与386兼容的System-V ABI上的 GCC (32位处理器) ,以下立场是:

普通位字段(即那些既不是带signed也不是unsigned )始终具有非负值。 尽管它们的类型可能为charshortintlong (可以具有负值),但这些位域的范围与大小相同且具有相应unsigned类型的位域的范围相同。 位域遵循与其他结构和联合成员相同的大小和对齐规则,并增加以下内容:

  • 位字段从右到左(从最低到最高)分配。
  • 位字段必须完全驻留在适合其声明类型的存储单元中。 因此,位域永远不会越过其单元边界。

  • 位域可以与其他struct / union成员(包括不是位域的成员)共享存储单元。 当然, struct成员占据存储单元的不同部分。

  • 尽管单个位域的成员偏移服从对齐约束,但未命名位域的类型不会影响结构或联合的对齐方式。

即在System-V ABI,386, int f: 1表示位域f必须int 如果剩余整个字节空间,则同一struct中的后续char打包在此int ,即使它不是位字段也是如此。

利用这些知识,

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;

将会

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

和布局

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;

将会

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

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

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

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

因为位域一定不能越过单元边界,所以struct x成员ab需要去不同的短裤。 则没有足够的空间容纳char c ,因此它必须在此之后。 并且整个结构必须short地对齐,因此在i386上将为6个字节。 然而后者,将包ab在的17最低位int ,并且由于仍然存在内留下一个整个寻址字节int ,该c这里将太包装,并因此sizeof (struct y)4


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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM