char和int

时间:2019-01-05 17:29:22

标签: c struct bit sizeof bit-fields

在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处。

1 个答案:

答案 0 :(得分:3)

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

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

关于C11/C17 6.7.2.1p5

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

,而您使用的是char,则意味着没有什么要讨论的一般内容-有关特定的实现,请查看编译器手册。 Here's the one for GCC

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


对于System-V ABI on 386-compatible (32-bit processors)上的GCC,以下立场:

  

纯白位字段(即signedunsigned都没有)始终为非负值。尽管它们的类型可能为charshortintlong(可以为负值),   这些位域的范围与相同大小的位域的范围相同   具有相应的unsigned类型。位域服从相同   大小和对齐规则与其他结构和工会成员一样,   以下内容:

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

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

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

  •   

即在System-V ABI 386中,int f: 1表示位字段f必须在int 之内。如果剩余整个字节空间,则在同一结构中的下一个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封装在int的最低17位中,并且由于int内还剩下一个完整的可寻址字节,因此{ {1}}也将放在这里,因此c将是 4


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