由浅到深认识C语言(12):位段/位域

该文章Github地址:https://github.com/AntonyCheng/c-notes

在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址:https://blog.csdn.net/AntonyCheng/article/details/136555245),该模板集成了最常见的开发组件,同时基于修改配置文件实现组件的装载,除了这些,模板中还有非常丰富的整合示例,同时单体架构也非常适合SpringBoot框架入门,如果觉得有意义或者有帮助,欢迎Star & Issues & PR!

上一章:由浅到深认识C语言(11):结构体

12.位段/位域

这里需要记住,在内存中数据插入输出的顺序,即逆序插入,顺序输出!详解在8.3指针变量中变量的类型;

问题提出:信息在计算机的存取长度一般以 Byte 为单位,但是有时候存储一个信息不必用到一个或者多个 Byte,例如"真"或"假"用 1 或 0 表示即可,只需要 1 bit,此时我们就需要使用位段;

**定义:**C语言允许在一个结构体中以 bit 为单位来指定其成员所占内存长度,以 bit 为单位的成员称为"位段"或称"位域",而且这些成员的数据类型大都为 unsigned int ,也可以用 unsigned char;

格式示例如下:

c 复制代码
struct data
{
	unsigned int a : 2;		//a占2位
	unsigned int b : 6;		//b占6位
	unsigned int c : 4;		//c占4位
	unsigned int d : 4;		//d占4位
	unsigned int i;			//i占32位
}data;

图解如下:

i d c b a
32bit 4bit 4bit 6bit 2bit

而且相邻位域是可以被压缩的,且相邻位域压缩的位数不能超过成员自身的大小;

解释如下: 以下数据类型仅为 unsigned int 和 unsigned char;

若相邻成员数据类型相同(可压缩):

  • 如果我们使用的是 unsigned int 类型,最多能够压缩 32 个连续的 unsigned int num : 1; 即最多压缩 32 个 0/1 编码,如果连续的相同数据类型成员位数超过了 4B ,即 32b ,如果大小溢出了 4B ,即大于等于了 33b 的话,进而成为 8B ,如果只压缩了 33 个 unsigned int num : 1 ,无论打印出来是多少 B ,实际大小还是为 33b ,往后以此类推;

  • 如果我们使用的是 unsigned char 类型,最多能够压缩 8 个连续的 unsigned char ch : 1; 即最多压缩 8 个 0/1 编码,如果连续的相同数据类型成员位数超过了 1B ,即 8b ,如果大小溢出了 1B ,即大于等于了 9b 的话,进而成为 2B ,如果只压缩了 9 个 unsigned char ch : 1 ,无论打印出来是多少 B ,实际大小还是为 9b ,往后以此类推;

若相邻成员数据类型不同(不可压缩):

不相邻的两个成员或者两类成员,则既要将两类数据内存按照 int 或者 char 基本数据类型的内存进行加减,又要将每一类中的各个成员内存进行压缩;

示例如下:

c 复制代码
#include<stdio.h>
#pragma pack(1)
struct bit_data
{
	unsigned char a : 1;
	unsigned char b : 1;
	unsigned char c : 1;
	unsigned char d : 1;
	unsigned char e : 1;
	unsigned char f : 1;
	unsigned char g : 1;
	unsigned char h : 1;
}data;

int main(int argc, char* argv[]) {
	printf("data = %d\n", sizeof(data));
}

打印效果如下:

12.1.位段的注意事项

  • 位段不能取地址:

    因为内存地址是以 Byte 为单位,因为系统内存是以 Byte 为基本单位,系统不会识别在 bit 上的地址;

  • 位段的赋值:

    不要操作越界的数据,解释如下:

    c 复制代码
    #include<stdio.h>
    
    struct bit_data
    {
    	unsigned char a : 1;	//0-1				==>		0-1
    	unsigned char b : 2;	//00-11				==>		0-3
    	unsigned char c : 3;	//000-111			==>		0-7
    	unsigned char d : 4;	//0000-1111			==>		0-15
    	unsigned char e : 5;	//00000-11111		==>		0-31
    	unsigned char f : 6;	//000000-111111		==>		0-63
    	unsigned char g : 7;	//0000000-1111111	==>		0-127
    	unsigned char h : 8;	//00000000-11111111	==>		0-255
    }data;
    
    int main(int argc, char* argv[]) {
    	data.a = 1;
    	printf("%u\n", data.a);
    	data.b = 3;
    	printf("%u\n", data.b);
    	data.c = 7;
    	printf("%u\n", data.c);
    	data.d = 15;
    	printf("%u\n", data.d);
    	data.e = 31;
    	printf("%u\n", data.e);
    	data.f = 63;
    	printf("%u\n", data.f);
    	data.g = 127;
    	printf("%u\n", data.g);
    	data.h = 255;
    	printf("%u\n", data.h);
    }

    打印效果如下:

  • 无意义位段:

    无意义位段用来限制压缩,解释如下:

    以下示例将位段定义成了全局变量,可以不用初始化为零值(因为全局变量会自动初始化),但是如果将该位段定义成局部变量,那么就需要我们手动初始化为零,因为局部变量载入时是一个不确定的值;

    c 复制代码
    struct bit_data
    {
    	unsigned char a : 2;
    	unsigned char : 2;    //无意义的位段,仅作占位符,占两个 bit
    	unsigned char b : 2;
    	unsigned char c : 2;
    }data;

    图示如下:

    c c b b a a

    验证如下:

    c 复制代码
    #include<stdio.h>
    #include<string.h>
    struct bit_data
    {
    	unsigned char a : 2;
    	unsigned char : 2;    //无意义的位段,仅作占位符,占两个 bit
    	unsigned char b : 2;
    	unsigned char c : 2;
    }data;
    
    int main(int argc, char* argv[]) {
        //如果是定义成了局部变量,还需要包含string.h头文件,然后:
        //memset(&data,0,1);
    	data.a = 0;
    	data.b = 2;
    	data.c = 3;
    	//此时data转换成二进制为 1110 0000 ==> 十六进制 0xe0
    	printf("data = %#x\n",data);
    }

    打印效果如下:

  • 另起一个位段:

    当我们将无意义位段设置为 0 时,我们就成功另起了一个位段,即在内存的另一处重新开辟了一个容器来装下一个位段的内容,证明如下:

    c 复制代码
    #include<stdio.h>
    #include<string.h>
    typedef struct data{
    	unsigned char a : 2;
    	unsigned char : 0;
    	unsigned char b : 2;
    	unsigned char : 0;
    	unsigned char c : 2;
    	unsigned char : 0;
    	unsigned char d : 2;
    }DATA;
    
    int main(int argc, char* argv[]) {
    	DATA data;
    	memset(&data, 0, 4);
    	//此时这个结构体里包含了四个位段地址
    	printf("sizeof(data) = %d\n", sizeof(data));
    	data.a = 0;
    	data.b = 1;
    	data.c = 2;
    	data.d = 3;
    	printf("%x\n", data);
    }

    打印效果如下:

12.2.位域和单片机

图解如下:

代码如下:

c 复制代码
#include<stdio.h>

typedef struct {
	unsigned char data : 1;
	unsigned char : 1;
	unsigned char addr : 2;
	unsigned char : 1;
	unsigned char opt : 2;
	unsigned char : 1;
}DATA;

int main(int argc, char* argv[]) {
	//下面是单片机定义
	DATA msg;
	msg.data = 1;
	msg.addr = 2;
	msg.opt = 1;
}

上一章:由浅到深认识C语言(13):共用体

相关推荐
Zwawa4 小时前
让屏幕动起来:OLED动画效果,丝滑光标、帧动画和一只贪吃蛇
c语言·单片机·嵌入式
Funlang5 小时前
龙芯上的 Fun:从跑起来到机器码
开发语言·龙芯
总要学点什么5 小时前
第104篇:三维特效-流动墙体效果——让贴图沿墙流动,它却只肯走一条对角线
开发语言·javascript·ecmascript·gis·webgl·材质·cesium
旖旎夜光5 小时前
【C++ 基础入门】C++11 新特性(中):右值引用、移动语义与完美转发
开发语言·c++·笔记·学习
ShineWinsu5 小时前
对于Qt:QTabWidget常见属性的解析
开发语言·数据库·c++·qt·浏览器·qtabwidget·标签页
普通人66 小时前
WebGL与WebGPU入门指南
开发语言·webgl·1024程序员节
꧁细听勿语情꧂6 小时前
直接选择排序,堆排序,冒泡排序,递归快速排序、hoare版本、挖坑法、lomuto前后指针,非递归快速排序
c语言·开发语言·数据结构·算法
SomeOtherTime6 小时前
Rust集合(AI回答)
开发语言·后端·rust
曦樂~7 小时前
【Web Development】03JavaScript 基础和事件
开发语言·前端·javascript
郝学胜-神的一滴7 小时前
LangChain Skill 实现(渐进披露 On-Demand Skill,LangChain 官方 Deep Agent Skill 模式)
开发语言·python·langchain·numpy