C语言 位域的使用

什么是位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个bit。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 bit即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。

所谓"位域"是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

典型的应用场景:

  • 用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
  • 读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。
  • 结构体中部分成员可能的赋值较小只需要用到1位或几位,可以把这类成员凑到一起减少整个结构体占用的空间。
  • 可巧妙用于位操作

位域的定义

位域定义与结构定义相仿,其形式为:

struct 位域结构名 
{
	位域列表
};

其中位域列表的形式为:

type [member_name] : width ;

其中 type :类型说明符; member_name: 位域名; width :位域长度;

例如:

struct TESTA
{
	char a:3;
	char b:5;
	char c:4;
	char d:4;
};

char 类型变量大小为一个字节(八位), 取值范围为(-128, 127)。在上述位域的定义中,
a 只取三位,取值范围为(-4, 3)。如果 a 为无符号型( unsigned char), 则取值范围为(0, 7)。

位域的使用

位域的使用和结构成员的使用相同,其一般形式为:
位域变量名.位域名

位域变量指针名->位域名
位域允许用各种格式输出。

示例:

int main()
{
    struct bs{
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    } bit,*pbit;
    
    bit.a=1;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.b=7;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.c=15;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    printf("%d,%d,%d\n",bit.a,bit.b,bit.c);    /* 以整型量格式输出三个域的内容 */
    
    pbit=&bit;    /* 把位域变量 bit 的地址送给指针变量 pbit */
    pbit->a=0;    /* 用指针方式给位域 a 重新赋值,赋为 0 */
    pbit->b&=3;    /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
    pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
    printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);    /* 用指针方式输出了这三个域的值 */
}

结果:
在这里插入图片描述

使用位域的注意点(重要)

1、位域成员必须声明为整型int、unsigned int或signed int类型,或是char,unsigned char,但不能是浮点型包括float,double
在ANSI C 中,这几种数据类型是signed int和unsigned int;到了C99、C11新增了_Bool 的位字段。支持char一般是由于编译器扩充的。

2、位域的长度不能超过它所依附的数据类型的长度,成员变量都是有类型的,这个类型限制了成员变量的最大长度,: 后面的数字不能超过这个长度。
例如:

struct k{
	int a : 33;
}

这里就会报错。

3、位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的(没有名称当然没法访问)。例如:

struct k{
    int a:1;
    int  :2;    /* 该 2 位不能使用 */
    int b:3;
    int c:2;
};

4、当无名位域且长度度为0时,表示下一个变量从下一段地址开始存储。(下一段地址的具体位置还得根据结构体的内存对齐原则)
示例:

#include <stdio.h>

struct k {
    char a : 2;
    char : 0;
    char b : 2;
    char c : 2;
};
struct k test;
int main() {
    char* ptr = (char *)&test;
    test.a = 1;
    test.b = 1;
    test.c = 1;
    printf("siezof(test)=%d\n",sizeof(test));
    printf("*ptr=%x ptr=%p\n", *ptr,ptr);
    printf("*ptr=%x ptr=%p\n", *(ptr+1), ptr + 1);
}

结果:
在这里插入图片描述
上述例程中,位域a独占一个字节,b和c共占一个字节。
b直接从下一个字节地址开始存储了。

5、不能使用位域的地址,如:

struct k {
    char a : 2;
    char : 0;
    char b : 2;
    char c : 2;
};
struct k test;
printf("&test.a=%p\n",&test.a);//报错 不能使用位域的地址

6、当结构体位域成员超出了限定的位数,将发生上溢(溢出中的一种)。

#include <stdio.h>

struct
{
	unsigned int age : 3;
} Age;

int main()
{
	unsigned int* ptr = &Age;
	Age.age = 4;
	printf("Sizeof( Age ) : %d\n", sizeof(Age));
	printf("Age.age : %d\n", Age.age);

	Age.age = 7;
	printf("Age.age : %d\n", Age.age);

	Age.age = 8; // 二进制表示为 1000 有四位,超出了定义的3位长度
	printf("Age.age : %d\n", Age.age);

	printf("*p=%x\n", *ptr);
	return 0;
}

结果:
在这里插入图片描述
注意,从最后的*p=0可以看出,溢出没有导致其他位发现改变,否则这里就会等于8了。

7、位域可以和正常的结构体成员写到一起。如:

struct
{
	unsigned int age1 : 3;
	unsigned int age2 : 3;
	unsigned int age3;
	unsigned int age4 : 3;
} Age;

8、一个位域存储在N个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。即不能跨字节存储位域。
举例:

struct pack
{
 unsigned int a:12;  
 unsigned int b:24;   
 unsigned int c:6;   
};

sizeof(struct pack) = 8

一个unsigned int是4字节,a占了12位,还剩20字节,b需要24位,已经不够了,又不能跨字节,因此b只能存在下一个unsigned int。
在这里插入图片描述
程序验证:

struct pack
{
	unsigned int a : 12;
	unsigned int b : 24;
	unsigned int c : 6;
};
struct pack test;
int main()
{
	unsigned int* ptr = (unsigned int *)&test;
	test.a = 0xFFF;
	test.b = 0xFFFFFF;
	test.c = 0x3F;
	printf("*ptr=%08X\n", *ptr);
	printf("*(ptr+1)=%08X\n", *(ptr+1));
}

结果:
在这里插入图片描述
由上面的测试可以验证,a和b之间的确有空的区域,即结果中的00000部分;

9、整个结构体空间占用大小
含位域的结构体占用存储大小规则大致如下:

  1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止
  2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
  3. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++,GCC采取压缩方式;
  4. 如果位域字段之间穿插着非位域字段,则不进行压缩(非位域字段保持其类型的对应大小);
  5. 整个结构体的总大小为最宽基本类型成员大小的整数倍。

第1,2点规则实际就是上面第8点所说的注意项问题,参考上文的示例即可。
第3点相邻的位域字段的类型不同,则各编译器的具体实现有差异举例:

#include <stdio.h>

struct test {
	char    a : 2;
	char    b : 3;
	int     c : 1;
};

int main(void)
{
	printf("%d\n", sizeof(struct test));
	return 0;
}

在Visual Studio 2019上运行结果:
在这里插入图片描述
在Linux上GCC编译运行结果:
4
(gcc version:4.8.2)

第四点如果位域字段之间穿插着非位域字段,则不进行压缩,指的是非位域字段不压缩

#include <stdio.h>

struct test {
	char    a : 2;
	char    b : 3;
	int     d ;
	int     c : 1;
};

int main(void)
{
	printf("%d\n", sizeof(struct test));
	return 0;
}

如上述示例程序struct test的d就是插在位域中的非位域字段,它的类型是int,所以d占4个字节,a和b共占4个字节,c单独占4个字节,因此sizeof(struct test) = 12

对于有位域的结构体占用存储大小的问题,做一个精简的方法总结:
带有’位域’的结构体并不是按照每个域对齐的,而是将一些位域 成员’捆绑’在一起做对齐的。"捆绑"还需注意跨字节问题,不能跨字节存储位域。位域捆绑后,按照普通结构体占用大小的规则计算即可。

实际应用

示例:

#include <stdio.h>

union  STATE
{
	struct  BITDATA
	{
		int  D0 : 1;
		int  D1 : 1;
		int  D2 : 1;
		int  D3 : 1;
		int  D4 : 1;
		int  D5 : 1;
		int  D6 : 1;
		int  D7 : 1;
	}BIT;
	int  value;
};


int main(void)
{
	int a = 0xFF;
	union STATE* sta;

	sta = &a;

	printf("sta.value=%02X\n",sta->value);
	printf("sizeof=%d\n", sizeof(sta->value));

	sta->BIT.D0 = 0;//给第一个位赋值

	printf("sta.value=%X\n", sta->value);
	printf("a=%X\n", a);
	return 0;
}

上述例程,使用union和位域实现对变量的某一bit位进行操作;该方式可用于计算机操作底层硬件寄存器。
结果:
在这里插入图片描述

参考:
https://blog.51cto.com/u_15244533/2845234
https://blog.51cto.com/u_14207158/2352294
https://blog.51cto.com/u_9233403/2121352
https://www.runoob.com/cprogramming/c-bit-fields.html
https://blog.csdn.net/sty124578/article/details/79456405

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾爱技术圈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值