C语言的位域

位域是什么?

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。

在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:

1struct bs{
2    unsigned m;
3    unsigned n: 4;
4    unsigned char ch: 6;
5};

:后面的数字用来限定成员变量占用的位数。成员 m 没有限制,根据数据类型即可推算出它占用 4 个字节(Byte)的内存。成员 n、ch 被:后面的数字限制,不能再根据数据类型计算长度,它们分别占用 4、6 位(Bit)的内存。

n、ch 的取值范围非常有限,数据稍微大些就会发生溢出,请看下面的例子:

 1#include 2int main(){3    struct bs{4        unsigned m;5        unsigned n: 4;6        unsigned char ch: 6;7    } a = { 0xad, 0xE, '$'};8    //第一次输出9    printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
10    //更改值后再次输出
11    a.m = 0xb8901c;
12    a.n = 0x2d;
13    a.ch = 'z';
14    printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
15    system("pause");
16    return 0;
17}

运行结果:

对于 n 和 ch,第一次输出的数据是完整的,第二次输出的数据是残缺的。

第一次输出时,n、ch 的值分别是 0xE、0x24('$' 对应的 ASCII 码为 0x24),换算成二进制是111010 0100,都没有超出限定的位数,能够正常输出。

第二次输出时,n、ch 的值变为 0x2d、0x7a('z' 对应的 ASCII 码为 0x7a),换算成二进制分别是 10 1101111 1010,都超出了限定的位数。超出部分被直接截去,剩下 110111 1010,换算成十六进制为 0xd、0x3a(0x3a 对应的字符是 :)。

C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。

例如上面的 bs,n 的类型是 unsigned int,长度为 4 个字节,共计 32 位,那么 n 后面的数字就不能超过 32;ch 的类型是 unsigned char,长度为 1 个字节,共计 8 位,那么 ch 后面的数字就不能超过 8。

我们可以这样认为,位域技术就是在成员变量所占用的内存中选出一部分位宽来存储数据。

C语言标准还规定,只有有限的几种数据类型可以用于位域。在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);到了 C99,_Bool 也被支持了。

但编译器在具体实现时都进行了扩展,额外支持了 char、signed char、unsigned char 以及 enum 类型,所以上面的代码虽然不符合C语言标准,但它依然能够被编译器支持。

位域的存储

C语言标准并没有规定位域的具体存储方式,不同的编译器有不同的实现,但它们都尽量压缩存储空间。

位域的具体存储规则如下:

  1. 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。

以下面的位域 bs 为例:

 1#include 2int main(){3    struct bs{4        unsigned m: 6;5        unsigned n: 12;6        unsigned p: 4;7    };8    printf("%d\n", sizeof(struct bs));9    return 0;
10}

运行结果:

m、n、p 的类型都是 unsigned int,sizeof 的结果为 4 个字节(Byte),也即 32 个位(Bit)。m、n、p 的位宽之和为6+12+4 = 22,小于 32,所以它们会挨着存储,中间没有缝隙。

sizeof(struct bs)的大小之所以为 4,而不是 3,是因为要将内存对齐到 4 个字节,以便提高存取效率。

如果将成员 m 的位宽改为 22,那么输出结果将会是 8,因为22+12 = 34,大于 32,n 会从新的位置开始存储,相对 m 的偏移量是 sizeof(unsigned int),也即 4 个字节。

如果再将成员 p 的位宽也改为 22,那么输出结果将会是 12,三个成员都不会挨着存储。

  1. 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。

请看下面的位域 bs:

 1#include 2int main(){3    struct bs{4        unsigned m: 12;5        unsigned char ch: 4;6        unsigned p: 4;7    };8    printf("%d\n", sizeof(struct bs));9    return 0;
10}

在 GCC 下的运行结果为 4,三个成员挨着存储;在 VC/VS 下的运行结果为 12,三个成员按照各自的类型存储(与不指定位宽时的存储方式相同)。

m 、ch、p 的长度分别是 4、1、4 个字节,共计占用 9 个字节内存,为什么在 VC/VS 下的输出结果却是 12 呢?期待您的回复。

声明:

本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xp精简工具_办公人士需要精简Win10吗?

首先要说自己的系统需求1,笔记本是主要生产力工具,靠它做方案,联系网络社交;2,会偶尔(一月2-3次)用到PS,AI,PR等软件;3,文件多,版本多…

matlab 删除路径_MATLAB使用教程(一)—新手来看

前言:在这里,本人默认大家已经安装好了MATLAB软件,如果没有,请自行安装。注意事项:新手容易犯错,打开MATLAB之后,请按CTRLQ这个组合键,是不是发现你的MATLAB关闭了?对&am…

C语言开发单片机为啥都是全局变量形式?

01前言全局变量简直就是嵌入式系统的戈兰高地。冲突最激烈的双方是:1. 做控制的工程师;2. 做非嵌入式的软件工程师。02做控制的工程师特点他们普遍的理解就是“变量都写成全局该有多方便”。我之前面试过一个非常有名的做控制实验室里出来的PhD/Master&a…

ajax get请求_python测试开发django50.jquery发送Ajax请求(get)

前言有时候,我们希望点击页面上的某个按钮后,不刷新整个页面,给后台发送一个请求过去,请求到数据后填充到html上,这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。Ajax可以完美的实现。…

primefaces_PrimeFaces Mobile入门

primefaces介绍 如果您已经开发了利用PrimeFaces的应用程序,或者打算开发可在台式机和移动设备上使用的Web应用程序,请考虑将PrimeFaces Mobile用于您的移动实施。 这篇博客文章将介绍一些基础知识,以帮助您开始为现有的PrimeFaces应用程序开…

C语言指针这些使用技巧值得收藏!

指针用的好犹如神助,用不好会让你叫苦连连,但大多数人是用不好指针的,所以后来的很多语言都把指针封装,屏蔽。比如JAVA,java是没有指针的,但是很多地方都用到指针,不过不对用户开放,…

mysql数据库中的int类型_MySQL中int(M)和tinyint(M)数值类型中M值的意义

在一开始接触MySQL数据库时,对于int(M)及tinyint(M)两者数值类型后面的M值理解是最多能够插入数据库中的值不能大于M;后来工作后,也是一边学习一边使用,之后的理解是其中的M的意思是插入数据库中的值的字符长度不能大于M&#xff…

api怎么写_使用Node.js原生API写一个web服务器

Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点。一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如Express和Koa。但是Express和Koa都是对Node.js原生API的封装,所以其实不…

C语言预处理命令分类和工作原理

C语言编程过程中,经常会用到如 #include、#define 等指令,这些标识开头的指令被称为预处理指令,预处理指令由预处理程序(预处理器)操作。相比其他编程语言,C/C 语言更依赖预处理器,故在阅读或开…

查看mysql用户权限_mysql 如何查看该数据库用户具有哪些权限?

展开全部背景在了解动态权限之前,我们先回顾下 MySQL 的权限列表。权限列表大体分为服务级别和表级别,列级别以32313133353236313431303231363533e59b9ee7ad9431333433633464及大而广的角色(也是MySQL 8.0 新增)存储程序等权限。我们看到有一个特殊的 SU…

注释嵌套注释_注释,无处不在的注释

注释嵌套注释十年前的2004年 , Java 1.5开始提供注释。 很难想象没有此功能的代码。 实际上,首先引入了注释,以减轻开发人员编写繁琐的样板代码的痛苦,并使代码更具可读性。 考虑一下J2EE 1.4(没有可用的注释&#xff…

a标签跳到另一个页面指定选项卡_HTML常用标签

本篇文章主要是对a、img和table标签用法介绍:a标签:可称为锚元素,主要功能是创建通向其他网页、文件、同一页面的其他位置、电子邮件地址或其他任何url地址的超链接;1.img标签:发出get请求并显示返回的图片1.常用属性&…

c语言也能用模板方法模式?

模式动机在嵌入式的应用场景中,管理资源(例如文件、内存)是一件非常麻烦、非常容易出错的事情。因为在分配资源后,还必须释放资源。例如fopen()打开文件后,必须要使用fclose()来关闭文件,而使用malloc申请内存资源后,就…

游戏 服务器 微服务_整体服务器与微服务

游戏 服务器 微服务介绍 刚开始时,由于要求简单,所以应用程序既简单又小。 随着时间的要求和需求的增长,我们的应用程序变得越来越大,越来越复杂。 这就导致了将单片服务器开发和部署为一个单元。 在某种程度上,微服务…

数据与ELF数据节-计算机系统基础题目

实验内容:修改二进制可重定位目标文件“phase1.o”的数据(.data)节内容(不允许修改其他节),使其与main.o模块如下链接后运行时输出目标字符串“123456789”。 实验步骤: 1. 使用objdump工具获得…

sqlserver拼接sql插入table_10个SQL技巧

介 绍为了理解这 10 个 SQL 技巧的价值,首先需要了解下 SQL 语言的上下文。为什么我要在 Java 会议上讨论 SQL 呢?(我可能是唯一一个在 Java 会议上讨论 SQL 的了)下面讲下为什么:从早期开始,编程语言设计者就有这种的愿望&#x…

【C语言笔记】指定初始化器

C99增加了一个新特性:指定初始化器(designated initializer)。利用该特性可以初始化指定的数组元素,也可以初始化指定的结构体变量。本文主要分享:使用指定初始化器初始化数组。例如,只初始化数组中的最后一个元素。对于传统的C初…

java锁性能对比_提高Java的锁性能

java锁性能对比Plumbr是唯一可以通过解释应用程序性能数据来自动检测Java性能问题根本原因的解决方案。 几个月前,我们在Plumbr中引入了锁定线程检测之后,我们开始收到类似于“嘿,太好了,现在我知道是什么导致了性能问题&#xf…

binlog日志_MySQL三大日志binlog、redo log和undo log

点击蓝色“JavaKeeper”关注我哟加个“星标”,一起成长,做牛逼闪闪的技术人Keeper导读:日志是mysql数据库的重要组成部分,记录着数据库运行期间各种状态信息。mysql日志主要包括错误日志、查询日志、慢查询日志、事务日志、二进制…

C 语言精髓之变参函数

我们以 printf 这个 very 熟悉的函数为例,来分析一下变参函数。先看下 printf 函数的定义:int printf(const char *fmt, ...){ int i; int len; /* va_list 即 char * */va_list args;va_start(args, fmt); /* 内部使用了 va_arg() */len v…