C语言基础知识:位与位字段

news/2024/11/9 0:51:40/

目录

位与字节

比特

字节

对齐特性

位字段


位与字节

二进制数系统中,每个0或1就是一个位(bit),位是数据存储的最小单位。其中8 bit就称为一个字节(Byte)。计算机中的CPU位数指的是CPU一次能处理的最大位数,例如32位计算机的CPU一次最多能处理32位数据,计算机中的CPU位数也成为机器字长,和数据总线(CPU与内部存储器之间连接的用于传输数据的线的根数)的概念是统一的。

比特


1) 计算机专业术语,是信息量单位,是由英文BIT音译而来。二进制数的一位所包含的信息就是一比特,如二进制数0101就是4比特。
2)二进制数字中的位,信息量的度量单位,为信息量的最小单位。数字化音响中用电脉冲表达音频信号,“1”代表有脉冲,“0”代表脉冲间隔。如果波形上每个点的信息用四位一组的代码表示,则称4比特,比特数越高,表达模拟信号就越精确,对音频信号信号还原能力越强。

字节

1字节(byte) = 8 比特(bit) 

注:这个字节与比特的关系是规定的,记住就好,通用于任何场景,容易混淆的是字长和字节,字长指的是cpu一次性能够运算的数据的位数,不同的计算机可能不一样,但是字节这个概念是恒久不变的。

对齐特性

对齐指的是如何安排对象在内存中的位置。
_Alignof运算符给出了一个类型的对齐要求,在关键字_Aligof后面的圆括号中写上类型名即可:

size_t  d_align  = _Alignof(float);

假定d_align的值是4,意思float类型对象的对齐要求是4。较大的对齐值被称为stricter或stronger,较小的值被称为weaker.
可以使用_Alignas说明符指定一个变量或类型的对齐值,但是不应该要求该值小于基本对齐值。

_Alignas(double) char c1;
_Alignas(8) char c2;
unsigned char _Alignas(long double) c_arr[sizeof(long double)];

注意:

不同版本的要求不同,有的要求_Alignas(type)说明符在类型说明符的后面,有的在前面,无论哪一种都能够识别。

位字段

位字段(bit filed)是C语言中一种存储结构,不同于一般结构体的是它在定义成员的时候需要指定成员所占的位数。位字段是一个signed int或unsigned int类型变量中一组相邻的位(C99和C11新增了Bool类型的位字段)。位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明建立了4个1位的字段: 

struct {unsigned autfd : 1;unsigned bldfc : 1;unsigned undln : 1;unsigned itals : 1;
} prnt;

根据该声明,prnt包含了4个1位的字段。现在,可以通过普通的结构成员运算符(.)单独给这些字段赋值:

    prnt.itals=1;prnt.undln=0;prnt.bldfc=1;prnt.autfd=0;

下面查看一下prnt的值:

    char str[33];int* value=reinterpret_cast<int*>(&prnt);itoa(*value,str,2);printf("%d %d %d %d\n", prnt.autfd,prnt.bldfc,prnt.undln,prnt.itals);printf("sizeof(prnt) = %d\n",sizeof(prnt));printf("十进制: %d\n",prnt);printf("二进制: %032s\n",str);

输出的结果为:
0 1 0 1
sizeof(prnt) = 4
十进制: 10
二进制: 0000 0000 0000 0000 0000 0000 0000 1010

可以看出,prnt的大小为4个字节(unsigned int 或 signed int),通过prnt的结构成员可以设置和访问某些bit位的值。
带有位字段的结构提供了一种记录设置的方便途径。许多设置(如,字体的粗体或斜体)就是简单的二进制一。例如,开或关、真或假。如果只需要使用1位,就不需要使用整个变量。内含位字段的结构允许在一个存储单元中存储多个设置。
有时,某些设置也有多个选择,因此需要多位来表示。例如,可以使用如下代码:

struct{unsigned code1 : 2;unsigned code2 : 2;unsigned code3 : 8;
}prcode;

这里创建了两个2位的字段和一个8位的字段,可以这样赋值:

    prcode.code1=0;prcode.code2=3;prcode.code3=102;

但是要确保赋的值不超出字段可容纳的范围(下面会说明当超出范围时会发生什么事情)。

再次打印出prcode的内容

    int* value_prcode=reinterpret_cast<int*>(&prcode);itoa(*value_prcode,str,2);printf("%d %d %d\n",prcode.code1,prcode.code2,prcode.code3);printf("sizeof(prcode) = %d\n",sizeof(prcode));printf("十进制: %d\n",prcode);printf("二进制: %032s\n",str);

打印结果如下:
0 3 102
sizeof(prcode) = 4
十进制:1644
二进制:0000 0000 0000 0000 0000 0110 0110 1100

可以看出,prcode.code1对应于0-1比特位,数值为00,对应十进制为0;
prcode.code1对应于2-3比特位,数值为11,对应十进制为3;
prcode.code2对应于4-11比特位,数值为0110 0110,对应十进制为102。
因此,一个字段可以对应于多个比特位,且当使用结构字段赋值在可容纳的范围之内时,变量可以记录正确的值。

这里再讨论一些需要注意的问题。首先是,如果声明的总位数超过一个unsigned int类型的大小(4 bytes)时会发生什么事情?结果是会用到下一个unsigned int类型的存储位置。一个字段不允许跨越两个unsigned int之间的边界。编译器会自动移动跨界的字段,保持unsigned int的边界对齐。一旦发生这种情况,第1个unsigned int中会留下一个未命名的“洞”。例如:

struct{unsigned a : 4;unsigned b : 4;unsigned c : 4;unsigned d : 25;
}prlimit;

上面定义的位字段大小共37个bits,超过了一个unsigned int的范围,给prlimit的各结构成员赋值,并使用下面代码打印出prlimit所存储的内容:

    prlimit.a=0xF;prlimit.b=0;prlimit.c=0xF;prlimit.d=0x1FFFFFF;char str_1[33];char str_2[33];int* value_1=reinterpret_cast<int*>(&prlimit);int* value_2=reinterpret_cast<int*>(&prlimit)+1;itoa(*value_1,str_1,2);itoa(*value_2,str_2,2);printf("0x%x 0x%x 0x%x 0x%x \n",prlimit.a,prlimit.b,prlimit.c ,prlimit.d);printf("sizeof(prlimit) = %d\n",sizeof(prlimit));printf("二进制 0~31位: %032s\n",str_1);printf("二进制 32~63位: %032s\n",str_2);

输出的结果如下:
0xf 0x0 0xf 0x1ffffff
sizeof(prlimit) = 8
二进制 0-31位:0000 0000 0000 0000 0000 1111 0000 1111
二进制 32-63位:0000 0001 1111 1111 1111 1111 1111 1111

从输出的结果可以看出,首先,prlimit的大小为8个字节;其次,编译器强制prlimit.d字段发生边界对齐,即prlimit.d位于第二个unsigned int上,prlimit.c与prlimit.d之间会填充未命名的“洞”。

实际上,我们也可以人为的在结构体当中设置未命名的字段宽度来进行填充。使用一个宽度为0的未命名的字段迫使下一个字段与下一个整数对齐:

struct {unsigned field1 : 1;unsigned        : 2;unsigned field2 : 1;unsigned        : 0;unsigned field3 : 4;
} stuff;

 使用下面的代码输出stuff的内容:

    stuff.field1=1;stuff.field2=1;stuff.field3=0xf;char str_1[33];char str_2[33];int *value_1=reinterpret_cast<int*>(&stuff);int *value_2=reinterpret_cast<int*>(&stuff)+1;itoa(*value_1,str_1,2);itoa(*value_2,str_2,2);printf("sizeof(stuff) = %d\n",sizeof(stuff));printf("二进制 0-31位: %032s\n",str_1);printf("二进制 32-63位: %032s\n",str_2);

输出的结果为:
sizeof(stuff) = 8
二进制 0-31位:0000 0000 0000 0000 0000 0000 0000 1001
二进制 32-63位: 0000 0000 0000 0000 0000 0000 0000 1111

也就是说,在这里,stuff.field1和stuff.field2之间,有一个2位的空隙;stuff.field3被强迫与下一个整数对齐,存储到下一个unsigned int中。stuff的大小为8个字节。

最后讨论一个当赋值超出字段可容纳范围的问题。

struct {unsigned t1 : 2;unsigned t2 : 3;unsigned t3 : 4;
} test;int main(){test.t1=3;test.t3=0;test.t2=11;printf("%d %d %d\n",test.t1,test.t2,test.t3);int *value=reinterpret_cast<int*>(&test);char str[50];itoa(*value,str,2);printf("二进制:%032s\n",str);return 0;}

输出的结果为:
3 3 0
二进制:0000 0000 0000 0000 0000 0000 0000 1111

上述代码中,成员test.t2赋值的大小超出了容纳的范围。可以看到,t2赋值为11(二进制是1011),结果输出的值是3(二进制是011),即截断了超出的部分。同时也可以看到,超出的部分不会影响t3的值(不同平台不一样?网上有人说会覆盖超出的区域)。

最后需要说明的是,字段存储在一个int中的顺序取决于机器。在有些机器上,存储的顺序是从左往右的,而在另一些机器上,是从右往左的。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植。尽管如此,有些情况却要用到这种不可移植的特性。例如,以特定硬件设备所用的形式存储数据。


http://www.ppmy.cn/news/76664.html

相关文章

node + alipay-sdk 沙箱环境简单测试电脑网站支付

正式上线需要上传营业执照&#xff0c;不知道怎么去申请一个。。。。。 使用沙箱测试&#xff0c;首先前往支付宝开放平台控制台可看到左下方的沙箱测试链接&#xff1a; 然后设置接口加签方式&#xff0c;选择系统默认密钥&#xff1a; 系统默认密钥 -> 公钥模式 -> 查看…

AI在狂飙,ChatGPT-4可直接在iPhone上使用啦

今天凌晨&#xff0c;OpenAI 正式在 App Store 推出了 ChatGPT 的 iOS app&#xff0c;瞬间冲上苹果商店免费榜第二名&#xff0c;效率榜第一名。 于是兴致勃勃的去下载体验了一番。整体不错&#xff0c;以后手机使用官方的 ChatGPT 更方便啦&#xff01;而且使用 GPT4 不再麻…

Mac ._ 开头的隐藏文件

在 MacOS 中&#xff0c;这些以 ._ 开头的隐藏文件或文件夹是由 Finder 创建的&#xff0c;用于存储文件的元数据信息。如果您想关闭这个功能&#xff0c;可以使用终端命令来禁用 Finder 创建这些文件或文件夹。以下是具体的步骤&#xff1a; 打开终端应用程序&#xff08;可以…

k8s使用私有镜像仓库的访问凭据配置

k8s创建私有仓库凭据有两种方式 1.使用kubectl命令创建secret 2.使用docker凭证作为k8s的凭据。 1.使用kubectl命令创建secret kubectl create secret docker-registry <name> --docker-serverDOCKER_REGISTRY_SERVER --docker-usernameDOCKER_USER --docker-passwor…

第14章:触发器概述

一、触发器概述 1.开发场景 有2个相互关联的表&#xff0c;商品信息和库存信息表。在添加一条新商品记录时&#xff0c;为了保证数据完整性&#xff0c;在库存表添加一条库存记录。 把两个关联操作步骤写到程序里面&#xff0c;用事务包裹起来&#xff0c;确保两个操作成为一…

是德科技keysight E8257D信号发生器

产品概览 Keysight E8257D (Agilent) PSG 模拟信号发生器提供业界领先的输出功率、电平精度和高达 67 GHz 的相位噪声性能&#xff08;工作频率可达 70 GHz&#xff09;。Agilent PSG 模拟信号发生器的高输出功率和卓越的电平精度通常无需使用外部放大器来测试高功率设备&…

漏洞扫描的原理

漏洞扫描是指通过自动或者手动的方式&#xff0c;对系统进行全面扫描&#xff0c;发现系统中存在的漏洞。随着互联网的发展&#xff0c;漏洞扫描的重要性越来越凸显&#xff0c;因为漏洞一旦被黑客利用&#xff0c;就可能会导致系统被攻击、数据被窃取等问题。那么什么是漏洞扫…

linux命令行目录操作命令

一&#xff0c;简介 本文主要介绍在linux过程中与目录相关等常用的操作命令&#xff0c;供参考。 二&#xff0c;命令介绍 2.1 目录相关命令 命令英文全称含义cdchange directory改变路径&#xff0c;修改路径pwdprint working directory打印当前所在路径mkdirmake directo…