C/C++位域结构深入解析

内存是以字节为单位进行编址的,编程语言的基本类型中,最小类型的长度一般也就是1个字节。然而,在解决某些问题时,必须要有二进制层面的表达手段(见本博客的自己动手实现DNS协议一文),又或者某些情形下根本用不着1个字节,作为强大到令人窒息的C/C++,难道没有解决方法?其提供的完美解决方法就是位域(位段)结构,本文将从定义、说明、内存布局和使用这四个方面对它进行详细的介绍。

1. 位域定义

首先,让我们看一下位域的定义。从定义中可以看出位域本质上其实就是结构体,只不过其成员都是按照特定长度的二进制位进行分配而已。

struct 位域结构体名
{
位域列表;
};
其中,位域列表为: 类型说明符 位域名: 长度;  组成

例如:

struct BitField
{
unsigned char a : ; // 第一个位域,2位
unsigned char b : ; // 第二个,4位
unsigned char c : ; // 第三个,2位
};

2. 位域说明

其次,我们来看一下位域的一些说明:

1. 位域可以没有名字的,这时它只用来作填充或调整位置。无名的位域是不能使用的

struct BitFiled_1
{
unsigned a : ;
unsigned : ; // 无名位域, 无法使用
unsigned b : ;
unsigned c : ;
};

2. 宽度为 0 的一个未命名位域强制下一位域对齐到其下一type位域的边界

struct BitFiled_1
{
unsigned a : ; // 第一个unsigned int,占4Byte
unsigned : ; // 未命名位域
unsigned b : ; // 从第二个unsigned int的4Byte开始存放,占4位
unsigned c : ; // 还是第二个unsigned int中的4位
// 该位域结构总共 8 Byte
};

3. 位域的长度不能大于其类型说明符中指定类型的固有长度,比如说int类型的位域长度不能超过32(bit),char的位域长度不能超过8(bit)

struct BitField_2
{
int a : ; // 编译错误,C2034,BitField_2::a位域类型的位数太小, >32
char b : ; // 编译错误,C2034,BitField_2::b位域类型的位数太小, >8
};

3. 位域内存布局

位域有一个非常重要的用途就是压缩存储,即:能够用1个比特解决的问题,绝不用2个比特。因此,我们非常有必要研究一下其内存布局,这样才能对其压缩存储特性有深入的了解。

1. 整个位域结构体的总大小为最宽基本类型成员大小的整数倍,这一点与常规结构体类型是一致的,从这里也可看出,位域本质上就是结构体;

2. 如果相邻位域字段的类型相同,且其声明的位宽长度之和小于类型的大小(sizeof获取的大小),则后面的位域字段将紧邻前一个字段存储,直到不能容纳为止;

// 假如 BitField_3::a = 0x11,(0001 0001 B); BitField_3::b = 0x2,(10 B);
// BitField_3::c = 0x35,(11 0101 B), 则有:
// 第一个4字节为:0001 0001 0000 0010 0000 0000 0000 0000(B)
// 第二个4字节为:0011 0101 0000 0000 0000 0000 0000 0000(B)
struct BitField_3
{
int a: ; // 第一个4Byte中的开始8bit
int b: ; // 由于相邻两个位域的类型相同,总大小10 < 32(int类型大小)
// 所以这里存储是挨着第一个4Byte,紧接着的2bit char c: ; // 由于相邻的两个位域类型不同
// 所以这里是第二个4Byte
// 总共8Byte
};

3. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的位域字段将从新的存储单元开始,其起始偏移量为类型大小的整数倍;

// 假如 BitField_4::a = 0x1,(0001 B); BitField_4::b = 0x08676665
// 则有:第一个4字节:0000 0001 0000 0000 0000 0000 0000 0000(B)
// 第二个4字节:0x65 66 67 08(小端模式,高高低低,高字节存放在高地址)
struct BitField_4
{
int a: ; // 第一个4字节 int b: ; // 虽然相邻位域类型相同
// 但是,4 + 29 > 32
// 因此b位域从第二个4字节开始存储
// 总共8字节
};

4. 如果相邻位域字段的类型不同,则各编译器的具体实现有差异,VC采取不压缩方式

// 假如 BitField_5::a = 0x4, (0100 B); BitField_5::b = 0x5, (0101 B), 则有:
// 第一个4字节:0000 0100 0000 0000 0000 0000 0000 0000(B)
// 第二个4字节:0000 0101 0000 0000 0000 0000 0000 0000(B)
struct BitField_5
{
int a : ; // 占据int的4字节中的4个bit
char b : ; // 相邻字段类型不同,就算类型是char,也另起一个4字节
// 总共8Byte,可见不但没有压缩,还浪费空间
};

5. 如果位域字段之间穿插着非位域字段,则不进行压缩;

// 假如 BitField_6::a = 0x4,(0100 B); BitField_6::b = 0x65, (0110 0101 B)
// BitField_6::c = 0x3,(0011 B), 则有:
// 第一个字节: 0000 0100 (B)
// 第二个字节: 0110 0101 (B)
// 第三个字节: 0000 0011 (B)
struct BitField_6
{
char a: ; // 第1个字节
char b; // 第2个字节,非位域
char c: ; // 第3个字节
};

注意:如果不是位域字段之间穿插着非位域字段,如下面这种情况,是进行压缩的:

// 以下为两个字节,可见进行了压缩存储
// 如果把 char a; 放到最后,只要不在中间,也是会压缩存储的
struct BitField_7
{
char a; // 第1个字节
char b: ; // 第2个字节中的4位
char c: ; // 第2个字节紧挨着的4位
};

6. 当使用有符号类型来定义位域,并且无意中使用到了正负(有意或者无意)特性时,就有问题了。

struct BitField_8
{
char a : ;
char b : ;
char c : ;
}; struct BitField_8 BF8; // 位域赋值
BF8.a = 0x3; //
BF8.b = 0x5; //
BF8.c = 0x2; // printf("%d,%d,%d\n", BF8.a, BF8.b, BF8.c); // OUTPUT: -1(0xff, 1111 1111), -3(0xfd, 1111 1101), 2(0x02, 0000 0110)
// 可见,当为域的最高位是1的时候,会进行符号扩展,而且这也取决于编译器的实现
// 因此,为避免此类问题,最好使用无符号类型定义位域 // 如果把BitField_8中的char换成unsigned char就没有问题了,输出是3, 5, 2,

从其内存布局可以看出,使用位域的最佳实践是:第一,位域的类型要使用无符号类型,并且在整个结构体内部要保持一致;第二,位域的总长度尽量与类型的长度保持一致;第三,不要在两个位域中间穿插非位域字段;如下代码所示:

struct BitFieldDemo
{
unsigned char a : ;
unsigned char b : ;
unsigned char c : ;
};

4. 位域使用

使用以下代码,再结合调试器的内存查看功能,即可清晰的验证本文 位域内存布局 一节所阐述的内容。本文使用编译器和调试器是Windows下的Visual Studio。

// 取位域大小,字节单位
int nsize = sizeof(struct BitFieldDemo); // 位域定义及其赋值
struct BitFieldDemo BFD; /* or = {0x3, 0x5, 0x2}*/
BFD.a = 0x3;
BFD.b = 0x5;
BFD.c = 0x2; printf("BFD.a = %d, BFD.b = %d, BFD.c = %d \n", BFD.a, BFD.b, BFD.c); // 内存拷贝
char szBuffer[] = "abcdefghijklmnopqrstuvwxyz0123456789"; struct BitFieldDemo *pBFD = NULL;
pBFD = (struct BitFieldDemo *)malloc(sizeof(struct BitFieldDemo));
if (pBFD != NULL)
{
memcpy(pBFD, szBuffer, sizeof(struct BitFieldDemo)); printf("a = %d, b = %d, c = %d \n", pBFD->a, pBFD->b, pBFD->c); free(pBFD);
pBFD = NULL;
}
原文

【转】C/C++位域结构深入解析的更多相关文章

  1. 15.5 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表

    点击返回:自学Zabbix之路 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表  1. Actions表 actions表记录了当触发器触发时,需要采用的动作. 2.Aler ...

  2. [转]谈NAND Flash的底层结构和解析

    这里我想以一个纯玩家的角度来谈谈关于NAND Flash的底层结构和解析,可能会有错误的地方,如果有这方面专家强烈欢迎指正. NAND Flash作为一种比较实用的固态硬盘存储介质,有自己的一些物理特 ...

  3. 自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表、Hosts_groups表、Interface表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表.Hosts_grou ...

  4. 自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表 Items表记录了i ...

  5. 自学Zabbix之路15.3 Zabbix数据库表结构简单解析-Triggers表、Applications表、 Mapplings表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.3 Zabbix数据库表结构简单解析-Triggers表.Applica ...

  6. 自学Zabbix之路15.4 Zabbix数据库表结构简单解析-Expressions表、Media表、 Events表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.4 Zabbix数据库表结构简单解析-Expressions表.Medi ...

  7. 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表  1. Actions表 ...

  8. activiti数据库表结构全貌解析

    http://www.jianshu.com/p/e6971e8a8dad 下面本人介绍一些activiti这款开源流程设计引擎的数据库表结构,首先阐述:我们刚开始接触或者使用一个新的东西(技术)时我 ...

  9. Nginx重要结构request_t解析之http请求的获取

    请在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 本文主要参考为<深入理解nginx模块开发与架构解析>一书,处理用户请求部分,是一篇包含作者理解的读书笔记.欢迎指正,讨论. ...

随机推荐

  1. 20155209林虹宇逆向及Bof基础实验报告

    20155209林虹宇逆向及Bof基础实验报告 实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符 ...

  2. WPF编程,通过Path类型制作沿路径运动的动画另一种方法。

    原文:WPF编程,通过Path类型制作沿路径运动的动画另一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/d ...

  3. LCA的一些算法

    LCA,就是求树上任意两点的最近公共祖先 (本题图片与代码均为Luogu3379) 方法我好像讲过一个,这次把主要的三个一起讲一讲 <1> 倍增(O(n log n)) 我们先考虑最基本的 ...

  4. mfc CListBox

    通过ID操作对象 CListBox(列表框)控件 CListBox类常用成员 CListBox插入数据 CListBox删除数据 CListBox运用示例 一.CListBox类常用成员 CListB ...

  5. stm32的PWM占空比

    PWM一共有两种模式,PWM1模式:CNT<CRRx为有效电平.CNT>CRRx为无效电平.PWM2模式相反. 有限电平通过设置极性来确定: TIM_OCInitStructure.TIM ...

  6. ASP.NET多行文本框限制字符个数

    asp.net中TextBox当设置TextMode = Multiline时,其MaxLength属性无效.可使用JS进行辅助限制输入的字符个数.中文算两个字符,西文算1个字符. TextBox属性 ...

  7. R实战 第六篇:数据变换(aggregate+dplyr)

    数据分析的工作,80%的时间耗费在处理数据上,而数据处理的主要过程可以分为:分离-操作-结合(Split-Apply-Combine),也就是说,首先,把数据根据特定的字段分组,每个分组都是独立的:然 ...

  8. Spring的单例模式底层实现学习笔记

    单例模式也属于创建型模式,所谓单例,顾名思义,所指的就是单个实例,也就是说要保证一个类仅有一个实例.单例模式有以下的特点:①单例类只能有一个实例②单例类必须自己创建自己的唯一实例③单例类必须给所有其他 ...

  9. Jmeter(二十二)_jenkins配置gitlab插件与ant插件

    Docker部署接口自动化持续集成环境第四步,代码上传到远程仓库! 接上文:脚本上传Gitlab 服务器中的Jenkins通过Gitlab插件读取远程Git远程仓库中的代码,然后通过ant插件进行构建 ...

  10. 【亲测有效】Win10家庭版Microsoft Edge页面出现乱码的两种解决方案及gpedit.msc命令无法使用的解决策略

    昨天在爬取电影的时候生成的表单打开result.html时,发现页面出现如下乱码: 第一种方法: 上网找了半天,网上的解决方案是这样的: 1.Win + R输入gpedit.msc打开组策略编辑器; ...