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

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

eg:

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

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

n、ch 的取值范围非常有限,数据稍微大些就会发生溢出。

eg:

  1. #include <stdio.h>
  2. int main(){
  3. struct test{
  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. return 0;
  16. }

运行结果:
0xad, 0xe, $
0xb8901c, 0xd, :

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

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

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

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

例如上面的test,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 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。

以下面的位域 test 为例:

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

运行结果:
4

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

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

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

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

请看下面的位域 test:

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

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

3) 如果成员之间穿插着非位域成员,那么不会进行压缩。

eg:

  1. struct test{
  2. unsigned m: 12;
  3. unsigned ch;
  4. unsigned p: 4;
  5. };

在各个编译器下 sizeof 的结果都是 12。

通过上面的分析,我们发现位域成员往往不占用完整的字节,有时候也不处于字节的开头位置,因此使用&获取位域成员的地址是没有意义的,C语言也禁止这样做。地址是字节的编号,而不是位的编号。

无名位域

位域成员可以没有名称,只给出数据类型和位宽,如下所示:

  1. struct test{
  2. int m: 12;
  3. int : 20; //该位域成员不能使用
  4. int n: 4;
  5. };

无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

上面的例子中,如果没有位宽为 20 的无名成员,m、n 将会挨着存储,sizeof(struct bs) 的结果为 4;有了这 20 位作为填充,m、n 将分开存储,sizeof(struct bs) 的结果为 8。

C语言结构体--位域的更多相关文章

  1. 关于牛客网C语言结构体位域(bit-fields)的一道题

    题目链接地址: https://www.nowcoder.com/questionTerminal/f4e20747a2dd4649bac0c028daa234f4 来源:牛客网 低地址字节 Byte ...

  2. C语言结构体位域

    demo: typedef struct { int a:2; int b:2; int c:1; }test; int main() { test t; t.a=1; t.b=3; t.c=1; / ...

  3. 解析C语言结构体对齐(内存对齐问题)

    C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...

  4. 漫谈C语言结构体struct、公用体union空间占用

    先用代码说话: #include<stdio.h> union union_data0{ int a ;//本身占用4个字节 char b ;//本身占用1个字节 int c ; }; u ...

  5. 不可或缺 Windows Native (8) - C 语言: 结构体,共用体,枚举,类型定义符

    [源码下载] 不可或缺 Windows Native (8) - C 语言: 结构体,共用体,枚举,类型定义符 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 结构体 ...

  6. Go语言结构体(struct)

    Go 语言结构体 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型. 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合. 结构体表示一项记录,比如保存图 ...

  7. C语言结构体定义的几种方法

    什么是结构体? 在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类.结构体可以被声明为变量.指针或数组等,用以实现较复杂的数据 ...

  8. 对嵌入式开发C语言结构体的一点总结

    今天冬至居然不上班,公司的良心啊!这回有心情写博客和日志了,好了,废话不多说.直接看下文: 鉴于嵌入式开发过程中,C语言结构体的使用当然是必不可少.话说,基础什么的比你会更牛逼的算法更重要,基础不牢, ...

  9. C语言结构体变量私有化

    操作系统 : CentOS7.3.1611_x64 gcc版本 :4.8.5 问题描述 C语言结构体定义中的变量默认是公有(Public)属性,如果实现成员变量的私有(Private)化? 解决方案 ...

随机推荐

  1. 【转】 Pro Android学习笔记(八六):了解Package(5):使用lib

    目录(?)[-] 在项目中使用lib 源代码 了解一些机制 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.net/flowin ...

  2. java代码swing编程JPaswordField类

    总结:JPasswordField类是JTextField类的子类.用户在JPasswordField对象中输入的字符会被其他的字符替代 而挡住,JPasswordFiled组件主要用来输入口令 pa ...

  3. MySQL执行计划的讲解

    最近同事在执行线上执行一条MySQL的查询语句,数据的话在9000条左右,但使用左连接的时候查询速度大概在15秒左右~这速度确实是无法接受的~ 经过简单的修改,变为内连接的话,执行速度不到1秒. 下面 ...

  4. DVWA平台v1.9-Command Injection

    命令拼接: &:简单的拼接,第一条命令和第二条命令间没有什么制约关系 &&:第一条命令执行成功了,才会执行第二条命令 |:第一条命令的输出作为第二条命令的输入 ||:第一条命令 ...

  5. CreateRemoteThread 远程注入

    在release中可以成功,在debug中被注入的程序停止工作 #pragma once#include "stdafx.h"#include <windows.h># ...

  6. CentOS 配置XWIN/VNC

    Xwin服务器 CentOS上运维Xwin,在这之前需要理清一些关系: 一,  X window 包括xserver 和x client.linux下的xserver 主要有xorg.xfree86, ...

  7. JavaScript组合模式---引入

    首先:使用一个例子来引入组合模式,需求为(1)有一个学校有2个班(一班,二班)(2)每个班级分2个小组(一班一组,一班二组,二班一组,二班二组)(3)学校计算机教室有限,每一个小组分着来上课 然后:根 ...

  8. windows系统中启动应用需要的端口被别的程序占用

    开始--运行--cmd 进入命令提示符 输入netstat -ano 即可看到所有连接的PID 之后在任务管理器中找到这个PID所对应的程序如果任务管理器中没有PID这一项,可以在任务管理器中选&qu ...

  9. for xml path 按分类合并行数据

    ) as itemnum FROM ( SELECT Sonum, (SELECT ItemNum+',' FROM testtb    WHERE Sonum=A.Sonum    FOR XML  ...

  10. Android Notification通知

    /** * 在状态栏显示通知 */ private void showNotification(){ // 创建一个NotificationManager的引用 NotificationManager ...