当前位置:   article > 正文

C语言——关于位段的理解_c语言 位段

c语言 位段

1.什么是位段

位段:以位为单位来定义结构体中的成员变量所占的空间内存。含有位段的结构体称为位段结构。
一个位段类型必须是int、unsigned int、signed int、char中的一种即整型,但不能是浮点型。
它的作用是利用最少的位数来存储数据,常用在通信协议方面等等。

2. 位段大小计算


#include <stdio.h>
struct S
{
	char a : 2;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };
	s.a = 3;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("size=%d\n", sizeof(s));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

第一次开辟1个字节(8位)的内存大小(不够再继续开辟)。
说明:第一次开辟的8个位中,a用去2个位,b用去4个,还剩2个位,但是c需要5个位,所以又开辟一个8位。
此时出现两种情况:
情况1、c用去上次开辟还剩的2个位,再用去新开辟的8个位中的3个位。
情况2、直接使用新开辟内存的5个位,上次剩的2个位被浪费。
C语言标准中没有被定义,所以不同的编译器会按照不同方式来计算。在VS2019编译器中,size是3,很显然,符合的是情况2,所以是被浪费掉的。

3. 位段的存储方式和内存分配

3.1存储方式

位段开辟内存空间的规则是:
1、按照4字节或者1字节来开辟。
2、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。(这一点非常重要!!!!!)
位段内存存储规则是:
1、一个位段存储单元能够全部储存位段结构中的所有成员,则该结构中所有成员只能放在一个位段存储单元里;反之,一个位段存储单元不能容纳下位段结构中的所有成员,则未被存储的成员从下一个位段存储单元开始存放。即不能跨字节存储。

typedef struct S1 {
	char a : 4;//下一个位段放在一个新的位段存储单元 ,所以该结构体占1+1=2字节
	char b: 5;     
} S1;//占字节数:1+1=2
  • 1
  • 2
  • 3
  • 4

2、第二点是非常需要注意的点!!!
在很多地方都会出现以下类似的语句:
如果一个位段结构中只有一个占有0位的无名位段,则只占1或0字节的空间(C中是占0字节,而C++中占1字节);否则其他任何情况下,一个位段结构所占的空间至少是一个位段存储单元的大小。

但是,这句话是不够严谨的。因为没有具体说明是哪一个编译器产生的结果,而位段本身就因为C标准的未定义,而出现不同的结果。

接下来,请看以下程序分析。编译器环境:VS2019
程序1:

#include <stdio.h>
struct S
{
	char : 0;
};
struct S1
{
	char  : 4;
}S1;

struct S2
{
	int : 0;
}S2;
struct S3
{
	int : 6;
}S3;

int main()
{
	struct S s ;
	struct S1 s1;
	struct S2 s2;
	struct S3 s3;
	printf("size=%d\n", sizeof(s));
	printf("size1=%d\n", sizeof(s1));
	printf("size2=%d\n", sizeof(s2));
	printf("size3=%d\n", sizeof(s3));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

编译后结果:
在这里插入图片描述
分析:通过验证发现,同char类型下,零位段的无名位段和非零位段的无名位段,所占字节数是不同的;同int类型下,零位段的无名位段和非零位段的无名位段,所占字节数是相同的。
结论:
1、位段结构中只存在一个零位段的无名位段,则所占空间应是一个位段存储单元即4字节。也可以理解一个int字节大小。
2、位段结构中只存在一个非零位段的无名位段,则所占空间应由位段类型决定。这里char类型就是1字节,int类型就为4字节。

程序2:

#include <stdio.h>
struct S
{
	char : 0;
	int : 0;
};
struct S1
{
	char  : 4;
	int : 6;
}S1;

struct S2
{
	int : 0;
	char : 0;
}S2;
struct S3
{
	int : 6;
	char : 4;
}S3;

int main()
{
	struct S s ;
	struct S1 s1;
	struct S2 s2;
	struct S3 s3;
	printf("size=%d\n", sizeof(s));
	printf("size1=%d\n", sizeof(s1));
	printf("size2=%d\n", sizeof(s2));
	printf("size3=%d\n", sizeof(s3));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

编译后结果:
在这里插入图片描述
分析:这里S1和S3的结果都是8字节,是符合结构体对齐规则的(详情请看我的另外一篇博客)。而S和S2的结果,可以得到第三个结论;位段结构中只存在n个(n>0)零位段的无名位段,其所占空间都是一个位段存储单元(一个int字节大小)。

总结:
结论1:位段结构中只存在n个(n>0)零位段的无名位段,其所占空间都是一个位段存储单元(一个int字节大小)。
结论2:位段结构中只存在n个(n>0)非零位段的无名位段,则所占空间应由位段类型、结构体成员数量以及定义顺序决定。(需要具体程序具体分析)

另外,零位段的无名位段其作用是使下一个位段从下一个存储单元开始存放。

综上所述,第二点规则是在探索中发现的现象,个人总结仅供参考,感兴趣的朋友也可以自己尝试验证。虽然在实际应用中,没有这样的情况出现,但是这个现象还是蛮有意思的。最重要的一点:关于位段问题的探索,绝对不能抛弃编译器,空泛而谈!!!

3.2内存分配

根据前面的计算发现,一个位段存储单元有剩余位数但是存不下下一个位段时,剩余位数是会被浪费。以下用程序来分析位段的内存分配问题。

#include <stdio.h>
struct S
{
	char a : 3;
	char b : 4;
	char c : 6;
	char d : 4;
};
int main()
{
	struct S s = { 0 };
	s.a = 3;
	s.b = 12;
	s.c = 5;
	s.d = 4;
	printf("size=%d\n", sizeof(s));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这里需要考虑一个问题,位段结构中的数据要么从右往左,要么从左往右。
这里我们假设从右往左,得到如下图。给结构体成员赋值后,借助编译器,观察成员在内存的存放是否一致。
在这里插入图片描述
查阅内存结果如下:
在这里插入图片描述
这特别说明一点,不同编译器的数据存储方式存在大小端模式,还有字节序和位序不同的问题。通过验证发现,VS2019在我的电脑上是以小端模式存储。

4. 位段的跨平台问题

关于位段涉及的诸多问题都是由于C语言标准未定义,所以编译器的不同出现的情况也可能不同,所以位段不能跨平台,其原因主要有以下几点:
1、int位段被当成有符号数还是无符号数是不确定的。
2、位段结构中最大位的数目不能确定。(16位机器最大为16,32位机器最大为32)
3、位段结构中的成员在内存中是从左向右分配,还是从右向左分配尚未定义。
4、当一个结构包含两个位段,第二个成员位段比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
所以在考虑是否使用位段时要十分小心,因为跟普通的结构体相比,位段虽然可以达到同样的效果,并且可以很好地节省空间,但是存在跨平台的问题。

5. 位段使用注意事项

1、位段的类型只能是int,unsigned int,signed int、char这四种类型,不能是浮点型;
2、位段所占位数不能超过该基本类型所能表示的最大位数,比如 int是占4个字节,那么最多只能是32位

//注意事项2
typedef struct S1
{
		unsigned int a : 1;
		char  b : 2;    //存在一个非0位的位段,则至少占4字节或者1字节;一个位段存储单元的大小是指该成员的类型(uint或者char)所占字节数
}S1;//占字节数为:4+4=8字节
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、无名位段不能被访问,但是会占据空间,且无名位段有两种形式:零位段的无名位段和非零位段的无名位段。这里出现的结果可以很好的符合了前面的结论。

//注意事项3:无名位段的两种形式
typedef struct S2
{
	int : 0;
	int : 8;   
}S2 ;//占字节数:4+4=8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4、不能对位段进行取地址操作,也不能出现数组的形式。
5、表达式中的位段会自动进行整型升级,自动转换为int型或者unsigned int。
6、对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。例如位段占2位,能表示的只有0、1、2、3,但给这个位段赋值却赋值为6,这是不可取的。

以上所述,皆为个人愚见,如有不妥之处,欢迎评论区指正或私信。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/515890
推荐阅读
相关标签
  

闽ICP备14008679号