C语言位域的使用,在开发中是非常常见的,比如各种协议的解析、各种文件格式的解析等都需要用到位域。因为为了充分利用内存或者减少通信量,定义各种协议格式等都是以位为单位,而不是以字节为单位。而对于位域的理解,很多人经常会感觉不清不楚。说会用也会用,但是也说不出所以然。
在开发的过程中,会为了节省内存,或者为了降低通信的信息量,而以位作为最小单位。通常用一个字节表示的一个状态,可以使用几个位组合起来表示,这样,一个字节可以存放多个状态,而不是多个字节,这样也就节省了大量的内存。内存开销变小,网络发送的数据包小了,因此效率也高了。
为了支持这样的需求,C语言就使用了位域。对于位域的理解,可以简单理解为一个结构体,用法也和结构体一样。不同的就是表示的单位变小。在这里就所有区别,这里也是让我疑惑的地方。而网上找了很多发现不是转载就是抄袭,对于我心里的疑问没有得到解答。
没有办法,又要自己研究一下了。通过实践来验证我的假设,然后来解决我的疑问。
首先我们来看看位域类型的定义、声明和使用。
不要觉得位域很神秘的样子,在定义位域类型、声明位域变量,和使用位域变量,和结构体没有两样,一模一样。那么此时,使用位域你是不是有了清晰的思路了呢?如果不清楚,那么请复习结构体的使用。
然后,结构体是以字节位最小单位的,而位域则是以位为最小单位,位域中的成员大小则是可以自己指定的。但是注意,位域成员的大小最大不能超过定义的类型的所占的位数。如:
typedef struct
{
int aa:5;
int bb:4;
int cc:31;
}BIT;
在此定义的位域结构体中,可以看到定义的形式与结构体基本一样,只不过,成员的定义则有点不同。毕竟是以位为基本单位,所以,使用了特定的语法。
用一个数据类型来定义,表示将这个类型所占的位数拿来拆分。这里是int,即32位。冒号后面的数字就是这个成员占用的位数,冒号前的字符串就是成员的名字。成员的名字如果不写,也可以,只不过不写就没法使用这个成员而已,纯粹就是占位而已。
既然是将成员前面的数据类型拿来拆分,也就表示,成员的位数不能超过这个数据类型所占用的位数。在此,int占用32位,也就是aa、bb和cc最大长度也只能是32位,超过后编译不通过。而如果不同的位域成员用不同的数据类型定义,也是可以的,那么此时就会有一个结构体内存对齐的问题。对齐相关的问题,请参考C++技术网其他文章。
为了不把问题复杂化,就只是用一个数据类型来定义。每一个位域成员设置一个位长度,连续的位域成员的长度,如果没有超过定义的数据类型所占的位数,那么就会依次的分布在这个内存中。它不再是以字节为单位,因此不要以字节的眼光来看待这个问题,否则会很难过的。而使用位的角度来看,依次划分即可。
但是,如果其中的一个或者几个位域成员的长度超过了基本单位后,内存大小如果变化的呢?关于这一点,网上的一些文章就模棱两可,不清不楚的了。这也是我要实践研究总结一下的原因了。
前面说了,位域定义的数据类型作为划分位的一个整体。如果几个位长度总和没有超过这个整体的位长度,那么就依次排列开来即可。但是如果前面几个的长度和后面一个加起来超过了这个整体,那么就不能再继续往后接着排了。就需要重新分配一个定义的数据类型的内存大小,从新分配的内存开始排,而前面几个没有用完的位,则空着。也就是说,虽然是来定义位的,但是数据单元还是要以定义位域成员的数据类型来申请内存。
以上面的例子说明,因为aa和bb的总长度是9,在32位的长度之内,因此,是依次排列的。然而,再加上cc,那么就显然超出了32位,此时就需要占用另外一个32位。而cc排列的开始是在第二个32位的起始位置的,中间的23位则是空白的,虽然也是浪费了,但是内存设计的不合理导致的,也是程序员的问题了。
附上验证代码:
#include <iostream>
using namespace std;
typedef struct
{
int a:3;
int b:4;
int c:31;
}BIT;
void main()
{
int bitsize = sizeof(BIT);
int intsize = sizeof(int);
BIT bit={0};
bit.a=1;
bit.b=2;
bit.c=3;
}