C语言中的位域

C语言中的位域

定义

    struct bit_fields{    /*位域名*/

       /*----- 位域列表------- */

      /*typename varname:bit_field_length, for example :  */

      int field1:2;

      char field2:3;

      uint64_t field3:8;

      /* ...  */

    };


用法

1.无名位域

用来填充和调整位置,硬件寄存器中经常有未使用的bit,可以用无名位域来对应这些bit,例如

typedef struct{
unsigned enable:1;
unsigned :2;
unsigned interrupt_enable:1;
}SPI_CR;

寄存器SPI_CR中enable位和interrupt_enable位中间空了2个bit,用unsigned:2无名位域来填充。

2.不能用来指定位数的类型

如果struct成员是指针变量不能用来指定所占的位数,在64位软件中指针固定站8字节,在32位软件中指针固定占4字节;

在struct成员是double或float类型,也不能指定位数,否则编译出错,位域类型无效。

3.压缩存储规则


    a.window vc10编译器:(在vs2010上测试)
        1.如果相邻位域字段的类型长度相同(只需要长度相同,无需类型严格一致),且它们的位数之和小于类型所占位数,则后面一个字段紧邻前一个字段存储,直到不能容纳为止;
        2.若不能容纳,则从新的存储单元开始,新的存储单元从类型所占字节数的整数倍处开始,符合结构体成员对齐准则,详见 https://blog.youkuaiyun.com/xiaoyink/article/details/80174062
        3.若相邻类型长度不同,则不压缩。
                如下type1中int a:2占4字节,char b:2不与int a合并,所以占1个字节,char d与char b合并,所以不单独占空间,int c 不与char b; char d; 合并,并且4字节对齐,所以char b; char d;之后空出3字节,然后是 int c占用4字节,至此,已占空间为12字节,uint64_t e不与int c合并,所以单独占用空间,且8字节对齐,所以前12字节后要补4字节,凑成16个字节,满足8字节对齐,然后 uint64_t占用8字节,总共24字节。
                若将uint64_t e换成uint32_t e,则e将和int c合并压缩,不在单独占用空间,总大小将变成12字节。希望通过此例能掌握vc10编译器中位域的使用。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>

struct type1{
    int a:2;
    char b:2;
    char d:2;
    int c:2;
    uint64_t e:2;
};

struct type2{
    int a:2;
    int b:2;
};

struct type3{
    int a:2;
    uint32_t b:2;
};

int main()
{
    printf("sizeof(type1) is %u\n", sizeof(struct type1));
    printf("sizeof(type2) is %u\n", sizeof(struct type2));
    printf("sizeof(type3) is %u\n", sizeof(struct type3));
    system("pause");
    return 0;
}

运行结果:

sizeof(type1) is 24
sizeof(type2) is 4
sizeof(type3) is 4
    b.linux gcc编译器:(gcc 4.9.2测试)

    1.无论相邻字段位域 类型长度是否相等,gcc都采用压缩的处理

    2.类型长度相等时,比较简单,基本和vc编译器一样,这里不再赘述;

    3.长度不同,首先要满足一条,整个结构体的长度为结构体中最长数据类型长度的整数倍,由于结构体成员的长度不同,每次需要开辟存储空间时开辟的长度与当前成员的类型长度相关,但最终都会以最长数据类型长度对齐,在关键位置放置合适的类型长度可以提高内存使用率,例如以下结构体    

struct op_code_t {
    unsigned na:4; 
    unsigned mod:6;
    unsigned pr:4;
    unsigned re0:6;
    unsigned re1:6;
    unsigned long immea:22;
    unsigned immeb:10;
    unsigned nb:6;
};//__attribute__((packed));

将immea声明为unsigned long类型,结构体的长度为8字节,在immea处,前面已经使用了26bit,immea类型长度为8byte,所以第一个存储单元的长度将延长至8byte,immea将占用22bit,总共48bit,小于64bit,不用开辟新的存储单元,immeb类型为unsigned,长度4byte,immeb所在的4字节已经别用掉了48%32= 16bit,剩余空间仍然足够存储10位,所以不用开辟新的存储单元,nb同理,所以最终结构体占8byte。

若在将unsigned long放置在其他任何位置,将immea成员换成unsigned,则整体大小变为16字节,如下:

struct op_code_t {
    unsigned na:4; 
    unsigned mod:6;
    unsigned pr:4;
    unsigned re0:6;
    unsigned long re1:6;
    unsigned immea:22;
    unsigned immeb:10;
    unsigned nb:6;
};//__attribute__((packed));

    如上例,首先编译器开辟第一个存储单元,长度为4byte,32bit足够na、mod、pr、re0使用,到re1时,re1类型变为unsigned long类型,第一个存储单元长度延长为8byte,至re1处,共用了26bit,至immea处,存储单元长度又变为sizeof(unsigned),immea所在4字节26%32=26bit,不足以存储immea,所以开辟新的存储单元,长度为4byte,当然,新开辟的存储单元和第一个长度为8byte的存储单元后4byte重合(由此可见,当前使用的存储单元的长度由当前成员变量的类型而定的,并不是一成不变的),immea和immeb正好32bit,刚好用完,到nb,开辟第三块存储单元,长度4byte,只使用了6bit,然后对第三块存储单元进行8byte对齐,最终长度为16byte。

所以规律如下:

        gcc计算存储单元的长度和当前成员的类型长度相等,若当前成员所在的空间除去之前成员使用的bit数,足够存储当前成员的bit数,则不开辟新的存储单元,否则开辟长度为当前成员类型长度的新的存储单元,直到结构体末尾,然后按照最长成员类型长度进行对齐。

    再举一例,将上述结构体改变如下:

struct op_code_t {
    unsigned char na:4; 
    unsigned mod:6;
    unsigned char pr:4;
    unsigned re0:6;
    unsigned long re1:6;
    unsigned immea:22;
    unsigned immeb:10;
    unsigned nb:6;
};//__attribute__((packed));

第一个存储单元仍为1字节,na使用了4bit,到第二个成员mod时存储单元长度变为4byte,mod使用6bit,加上之前的4bit,总共使用10bit,到pr时,存储单元长度又变为1byte,pr所在的字节,已经被mod用了10%8=2bit,剩余6bit,pr使用4bit,最终剩余2bit;到re0时存储单元长度变为4byte,前面已经使用了4+6+4=14bit,re0使用6bit,至此共使用20bit;到re1处,存储单元长度变为8byte,共使用26bit;至immea存储单元长度变为32bit,已使用26bit,开辟第二块存储单元,长度为4byte,immea存储到第二块存储单元,immeb紧随其后,nb开辟新的存储单元,最后8byte对齐,总共占用16byte。

    上例中如果使用gccde _attribute__((packed))属性则,结构体之间无间隙,所有的位将紧挨在一起,共8byte,详见:

http://blog.shengbin.me/posts/gcc-attribute-aligned-and-packed

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值