位段的声明 以及 分配内存的方式

位段是结构体的另一种功能,有结构体的地方就能使用位段,位段能够准确分配变量所占大小,使用巧妙的话,可以节省很多空间,但是适用的变量类型只能是整型家族的,比如int 、char等类型。


目录

一、位段的声明和初始化

1、位段的声明

2、位段的初始化

二、位段内存的开辟方式

1、声明阶段 

(1) 第一个成员

(2) 第二个成员

(3) 第三个成员

(4) 第四个成员

2、初始化阶段

(1) 第一个成员

 (2) 第二个成员

(3) 第三个成员

(4) 第四个成员

3、代码验证

三、位段的跨平台问题

1、int位段处理问题

2、位段最大bit位的问题

3、空间使用的顺序问题

4、位段空间开辟存在的问题

四、位段的应用

五、总结


一、位段的声明和初始化

1、位段的声明

位段的声明如下:

struct 结构体名
{
    位段成员类型 位段成员名: 分配的内存大小 ;
}

//举例
struct S
{
    int a: 2;    //分配 2bit 的内存大小(2 bit是站在二进制的角度来分配的)
    int b: 5;    //分配 5bit 的内存大小(下同)
    int c: 9;
    int d: 4;
}

以成员 _a 为例,_a 是 int 类型,本该分配的是 4个字节(4 byte = 32 bit),但是实际上, _a 只需要 2 bit 就够了,为了节省空间,我们就通过位段声明只给 _a 分配了 2 bit。 

2、位段的初始化

初始化的方式跟结构体一样,但是会根据分配的 bit位 进行略微调整。

//初始化
struct S s = {0};
s.a = 11;       // 截短
s.b = 12;
s.c = 'x';      // 补全 
s.d = 4;

位段初始化的方式和结构体是一样的,但是需要注意的是,每一个成员都分配了固定大小的内存,在初始化的时候,要根据分配的大小来决定是截短还是补充。

=================== 第一步 ===================

结构体的第一个成员是一个int类型,一开始会在内存里开辟 4 个字节的大小

 =================== 第二步 =================== 

变量 a 占 2 bit,11是int型(32 bit),此时很显然需要截短,只保留低 2 位。然后把低2位放入到上面开辟的空间中。

后面也是以此类推,具体的将在下面的案例中介绍。

二、位段内存的开辟方式

位段成员必须是属于整型家族类型,位段在内存上的开辟方式是以 4 个字节 或者 1个字节的方式来开辟的。根据成员类型判断,如果是int类型,那么就以4字节开辟;如果是char类型,则以1字节开辟。

以下面这个位段为例,我们通过逐个给位段成员分配空间来了解 位段开辟空间的方式。

1、声明阶段 

//声明
struct S
{
    char _a: 3;       //分配3bit
    char _b: 4;       //分配4bit
    char _c: 5;
    char _d: 4;
}

(1) 第一个成员

第一个成员是char类型,最开始开辟空间的时候,分配的是 1个字节(即8 bit),第一个成员被分配了 3 bit。这里的占据方式没有固定的标准,会随编译器的不同而不同。假设这里就从低位开始排

(2) 第二个成员

第二个成员分配了4 bit,我们可以继续在 _a 的前面占据 4 bit。

(3) 第三个成员

第三个成员分配了 5bit,很显然,剩下的位置不够放了,这个时候 编译器就会再给我们 开辟 1个字节(8 bit)的空间,这就解释了最开始的那句话 “位段在内存上的开辟方式是以 4 个字节 或者 1个字节的方式来开辟的”,每当位置不够的时候,根据类型分配相应的空间

新开辟的空间就往后排,既然上一段空间无法放下第三个成员,我们就把第三个成员放到新开辟的空间上。

(4) 第四个成员

第四个成员分配了 4bit,很显然,上一段空间又不够放了,所以我们舍弃剩余的空间,重新开辟一个新的空间,第四个成员是 char 类型就开辟 8 bit的空间,然后在新开辟的空间上占据 4 bit。

2、初始化阶段

初始化阶段需要注意最开始说的规则,溢出就阶段,缺少就补全。

//初始化
struct S s = {0};
s._a = 10;
s._b = 12;
s._c = 3;
s._d = 4;

(1) 第一个成员

第一个成员是 _a ,初始化的值是 10(对应的二进制数为1010),但是 _a 只分配了3bit,因此需要截短,保留低三位,舍弃最高位。

 (2) 第二个成员

第二个成员是 _b,初始化的值是 12(对应的二进制数为 1100),_b正好分配了 4bit,可以直接填进去。

(3) 第三个成员

第三个成员是 _c,初始化的值是 3(对应的二进制数为 11),_c分配了 5bit,需要补全三位,即变成00011。

(4) 第四个成员

第四个成员是 _d,初始化的值是4(对应的二进制数为 100),_d分配了4bit,补全一位变成 0100。

现在四个位段成员已经全部放置完,我们将上面这串二进制数四个为一组计算,因此位段成员在内存里的排列为 62 03 04

3、代码验证

//声明
struct S
{
    char _a : 3;       //分配3bit
    char _b : 4;       //分配4bit
    char _c : 5;
    char _d : 4;
};

int main() {
    //初始化
    struct S s = { 0 };
    s._a = 10;
    s._b = 12;
    s._c = 3;
    s._d = 4;

	return 0;
}

下面我们采用调试的方式来看一下内存里的排列方式,和我们上面逐步存放每一个成员的结果一样。

三、位段的跨平台问题

1、int位段处理问题

我们在最开始说过,位段必须是整型家族的一员,int作为整型家族的一员,是看作无符号整型还是有符号整型,这个并没有做出明确的规定。

2、位段最大bit位的问题

一个位段成员所分配的 bit数 是不能超过自身类型限制的bit数的,一个int类型占 4字节(32bit),此时你分配30bit是没有问题的。

==》如果放到早期的 16位机器上,16位机器上的int类型占 16bit,而我们的 _d 是30 bit,此时就会有问题;

==》如果放到 64 位机器却没有问题,因为64位机器上一个int类型 也是占 4字节(32 bit),_d 的30bit没有超过32bit,所以没有问题。

struct S
{
    int _d: 30;
}

3、空间使用的顺序问题

我们在分配第一个成员 _a 的内存的时候,存在这么一个问题,我们分配的时候,是从左向右使用,还是从右向左使用?这个并没有给出明确的标准,但是从我们最后的结果来看,VS编译器选择的是从右向左使用。

4、位段空间开辟存在的问题

我们在给前两个成员开辟空间以后,第一个空间还剩下 1bit,但是无法容纳第三个成员,此时第三个成员是把第一个空间填满再放到新开辟的空间,还是直接全部放到新的空间,这个没有明确的规定。VS编译器选择的是直接全部放到新的空间。

四、位段的应用

最典型的应用就是 UDP数据包,我们在QQ、微信发送的消息在网络中不是裸奔的,而是以数据包的形式展现的。

网络传输就像是高速公路,车流量越小时就很通畅,车流量较大时就很堵,网络传输也是一样,一个端口号明明只要 16 bit,我们却整了个 32 bit,我们在传输的时候就要多传输16bit的无用信息,同时网络中可能会存在上万个数据包,每个都多出 16 bit,这很显然会降低网络的传输速度。

像下面的UDP数据包,如果你这样看着不是特别舒服,可以看第二张图,我们可以发现,没有任何一个bit被舍弃,反倒得到了充分使用。

五、总结

跟结构体相比,位段也能达到存放变量的效果,而且比结构体更节省空间,但是存在跨平台的问题。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值