【自定义类型详解】第二篇——结构体实现位段

简介: 【自定义类型详解】第二篇——结构体实现位段

前言

上一篇文章,我们一起学习了结构体,那学完了结构体,就一定得讲讲结构体实现位段的能力。

这篇文章,我们再来一起学习一个新知识——位段。

一起来学习吧!!!


1.什么是位段

首先,我们一起来了解一下什么是位段。


位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。


位段和结构体其实是非常相似的,但是有两个不同点:


1. 位段的成员必须是 char、int、unsigned int 或signed int 。

2. 位段的成员名后边有一个冒号和一个数字。


举个例子:

struct A
{
  int _a : 2;
  int _b : 5;
  int _c : 10;
  int _d : 30;
};

那里面的冒号和数字又表示什么呢?


首先我们要明白位段中的这个“位”字其实指的是二进制位。

我们知道一个二进制位就是1个比特位。

所以,A中int _a : 2;其实表示的就是

_a的大小是2bit;

同理:

_b的大小是5bit

_c的大小是10bit

_d的大小是30bit


那我们再来思考一个问题:


位段A的大小应该是多少呢?

是30+10+5+2=47个bit吗,但我们知道sizeof计算出来的是字节数啊,所以会给它分配6个字节=48个比特位吗。


我们验证一下:

05ee34d9f6ec4bc28ab8c3f8adc4b953.png

是8个字节哎,为什么呢?

别着急,我们接着往下看。


2.位段的内存分配

我们已经知道:


位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型。


那位段所需要的空间是怎么来开辟的呢?


位段的空间上是按照需要以每次4个字节( int )或者1个字节( char )的方式来开辟的。


什么意思呢,解释一下:


就是说,如果位段的成员全部是整型的(位段成员一般都是同类型的),那上去就先给这个位段开辟4个字节的空间,如果不够用,放不下所有的成员,那就再开辟4个字节的空间,还不够用,继续开辟,以此类推。

如果成员全部是char类型的,那就一次开辟1个字节的空间,直至放得下所有成员。


好,那我想现在大家就明白为什么上面位段struct A的大小是8个字节了。


由于A的成员都是整型(int ),所以一次给A分配4个字节。

4个字节是32给比特位,A的前3个成员_a、_b、_c占了17个bit,32-17还剩15bit,但是A的第四个成员_d大小是30bit,而15<30不够。

怎么办?

再分配4个字节,这下就能放下_d,因此,struct A的大小是4+4=8个字节。


那现在又有一个问题:

对于刚才讨论过的struct A来说,上来先给它分配了4个字节及32比特位,前3个成员占了17个bit,32-17还剩15bit,然后这15bit留给第四个成员_d不够用,所以又开辟4个字节。


那这15bit以及新开辟的4个字节的空间是怎样给第四个成员_d分配的,有两种情况:


_d的大小是30bit,它先使用了前面剩下的15bit,然后又使用了新开辟的4个字节中的15bit。

前面剩下的15bit,就直接浪费掉不用了,_d直接使用新开辟的4个字节中的30个bit。

那到底采用的是哪种呢?其实C语言的语法也并没有做出明确的规定:


位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。


3.验证vs环境下位段成员如何分配内存空间

虽然是不确定的,但是我们可以来验证一下,在我们当前使用的环境(vs2022)下,位段开辟的空间是怎么分配给每个成员的。

接下来,我们就通过一个实例来探究一下:

struct S 
{
  char a : 3;
  char b : 4;
  char c : 5;
  char d : 4;
};
int main()
{
  struct S s1 = { 0 };
  return 0;
}

我们先来思考一下,s1 的大小是多少,并猜测一下这些空间是如何分配给每个成员的。

s1 的每个成员都是char类型的,所以应该每次开辟1个字节。

  1. 首先分配的一个字节8个比特位,是可以放得下a,b的,我们假设是这样放的:

6bd26083df504500b2c1c37bd7149db4.png再放c的话,只剩1个比特位,就不够了,所以需要再开辟1个字节的空间,那剩下的这1个比特位用不用呢,反正是不确定的,这里我们就假设再vs上不再使用剩下的这1个比特位了。


b79496fce8e1470ca7f32fbfcdb7c9a5.png

.那接着就要再开辟一个字节,因为还有一个成员

0b8f663f669040d7abfb2ae50ffa1923.png

那vs环境下的位段变量s1的大小会是3个字节吗?

我们验证一下:

ef76474028024c13a2a4cd0fdd9dc5da.png

确实是3,证明我们的猜测大致是正确的。

接下来,我们再来进一步的验证一下:

int main()
{
  struct S s1 = { 0 };
  s1.a = 10; 
  s1.b = 12; 
  s1.c = 3; 
  s1.d = 4;
  printf("%d", sizeof(s1));
  return 0;
}

我们先把位段变量s1 的成员都赋值为0,然后给他们重新赋一个非0值,最后,我们借助编译器观察一下,各个成员再内存中的存放是不是跟我们上面分析的一样。

7c9c4c620f3a48b3a7fec97619c74068.png

b8d784bbcb11457b9b33276456495833.png

现在我们通过编译器观察一下:


625ee4289a29437695e2c204f7199d16.png

结果验证了在vs上位段的内存分配就是这样搞得,那在其它的平台上是不是也是这样呢?


不是的,前面已经提到了,这是不确定的,是标准未定义,在其他编译器上,可能结果就不一定是这样了。

位段是不跨平台的。


4.位段的跨平台问题

上面我们提到位段是不跨平台的,那接下来我们就来讨论讨论位段的跨平台问题。


1. int 位段被当成有符号数还是无符号数是不确定的。

什么意思呢,解释一下:


我们还拿上面的代码来说

struct A 
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

位段A的成员都是int 类型的,但是我们这样直接给一个int ,它到底会被当成有符号int 还是无符号int 是不确定的

2. 位段中最大位的数目不能确定。

什么意思呢,再把上面的代码拿过来:

struct A 
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

这里面成员_d给的大小是30比特位。

但是在16位的机器上,int 类型的大小是2个字节=16个比特位,即在16位的机器上最大的位数才16位。

那如果我们把int _d:30;的代码放在16位机器上,是不是就出错了啊。


3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

再来看一张上面用过的图:

6e56cf58142d4119a0a733f76e430550.png我们当时为什么这样放,是不是我们假设的啊,我们假设位段的成员再内存中是从右向左分配的。

为什么假设,因为这时标准未定义的,在不同的平台上可能就是不一样的。


4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

这个问题我们其实在上面也已经提到过了,再来看一张上面用过的图:

a5f1a906895344ac8d6095d77b03faf1.png

这张图上,当剩余的空间无法容纳后面的位段成员时,我们就是把剩余的空间舍弃了,去使用新开辟的空间来放后面的成员,而在vs上也就是这样做的。

当然它也是不确定的,在不同平台可能不同。


总结


跟结构体相比,有时候位段可以达到和结构体同样的效果,而且可以很好的节省空间,但是有跨平台的问题存在。

struct A //位段
{
  int _a : 2;
  int _b : 5;
  int _c : 10;
  int _d : 30;
};
struct B //结构体
{
  int a;
  int b;
  int c;
  int d;
};
int main()
{
  printf("%d\n", sizeof(struct B));
  printf("%d\n", sizeof(struct A));
  return 0;
}

比较一下它们的大小:

a9d7a306ddd64b36933051ff73dc0eaf.png

以上内容就是对结构体实现位段的一个讲解,欢迎大家指正!!!

3efccaa957114275b684874fbd3cd7e4.png


目录
相关文章
|
编译器
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(上)
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(上)
|
7月前
|
存储 C语言
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(下)
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)
49 0
|
7月前
|
存储 C语言
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(中)
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)
43 0
|
7月前
|
编译器 C语言 C++
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(上)
C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)
24 0
|
7月前
|
存储 网络协议 编译器
【C语言】自定义类型:结构体深入解析(三)结构体实现位段最终篇
【C语言】自定义类型:结构体深入解析(三)结构体实现位段最终篇
|
7月前
|
编译器 Linux C语言
C语言:结构体(自定义类型)知识点(包括结构体内存对齐的热门知识点)
C语言:结构体(自定义类型)知识点(包括结构体内存对齐的热门知识点)
|
存储 C语言 C++
【C语言】一篇让你彻底吃透(结构体与结构体位段)(下)
【C语言】一篇让你彻底吃透(结构体与结构体位段)(下)
112 0
|
7月前
|
存储 编译器 C语言
c语言进阶部分详解(详细解析自定义类型——结构体,内存对齐,位段)
c语言进阶部分详解(详细解析自定义类型——结构体,内存对齐,位段)
75 0
|
存储
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(中)
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(中)
|
存储 编译器
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(下)
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(下)

热门文章

最新文章