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


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


    目录

    一、位段的声明和初始化

    1、位段的声明

    2、位段的初始化

    二、位段内存的开辟方式

    1、声明阶段 

    (1) 第一个成员

    (2) 第二个成员

    (3) 第三个成员

    (4) 第四个成员

    2、初始化阶段

    (1) 第一个成员

     (2) 第二个成员

    (3) 第三个成员

    (4) 第四个成员

    3、代码验证

    三、位段的跨平台问题

    1、int位段处理问题

    2、位段最大bit位的问题

    3、空间使用的顺序问题

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

    四、位段的应用

    五、总结


    一、位段的声明和初始化

    1、位段的声明

    位段的声明如下:

    1. struct 结构体名
    2. {
    3. 位段成员类型 位段成员名: 分配的内存大小 ;
    4. }
    5. //举例
    6. struct S
    7. {
    8. int a: 2; //分配 2bit 的内存大小(2 bit是站在二进制的角度来分配的)
    9. int b: 5; //分配 5bit 的内存大小(下同)
    10. int c: 3;
    11. int d: 4;
    12. }

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

    2、位段的初始化

    位段初始化的方式和结构体是一样的,但是需要注意的是,每一个成员都分配了固定大小的内存,此时就会满足一个原则 “溢出就截短,不够就补全”。下面来解释一下。

    以第一个成员为例,成员 a 被分配了 2 个bit,站在二进制的角度就是分配了 00,而初始化的时候,11 的二进制数是 1011,超出了2bit,因此实际分配给 a 的只有 11,高两位会被舍弃。

    以第三个成员为例,成员 c 被分配了 3 bit,站在二进制的角度就是分配了 000,初始化的值为3,对应的二进制数为 11,少了一位,此时就需要在最开始补全一位,默认补 0。

    1. //初始化
    2. struct S s = {0};
    3. s.a = 11; //被截短,a 分配了2 bit,取值范围为 0~3
    4. s.b = 12;
    5. s.c = 3; //要补全,c 分配了3 bit,取值范围为 0~7
    6. s.d = 4;

    二、位段内存的开辟方式

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

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

    1、声明阶段 

    1. //声明
    2. struct S
    3. {
    4. char _a: 3; //分配3bit
    5. char _b: 4; //分配4bit
    6. char _c: 5;
    7. char _d: 4;
    8. }

    (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、初始化阶段

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

    1. //初始化
    2. struct S s = {0};
    3. s._a = 10;
    4. s._b = 12;
    5. s._c = 3;
    6. 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、代码验证

    1. //声明
    2. struct S
    3. {
    4. char _a : 3; //分配3bit
    5. char _b : 4; //分配4bit
    6. char _c : 5;
    7. char _d : 4;
    8. };
    9. int main() {
    10. //初始化
    11. struct S s = { 0 };
    12. s._a = 10;
    13. s._b = 12;
    14. s._c = 3;
    15. s._d = 4;
    16. return 0;
    17. }

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

    三、位段的跨平台问题

    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,所以没有问题。

    1. struct S
    2. {
    3. int _d: 30;
    4. }

    3、空间使用的顺序问题

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

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

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

    四、位段的应用

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

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

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

    五、总结

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

  • 相关阅读:
    基于VitePress创建组件文档
    阿里云2核4G服务器5M带宽五年租用价格表
    Android热修复2(ASM技术的运用)
    【无标题】
    Python爬虫如何设置代理服务器(搭建代理服务器教程)
    迪赛智慧数——其他图表(基本旭日图):毕业演讲高频词
    Linux 通过yum源安装subversion(svn)服务端
    springboot/ssm甘肃印象网站Java地区特产文化交流管理系统web
    OpenCV快速入门:绘制图形、图像金字塔和感兴趣区域
    快来跟我一起抢先看看未来世界的出行,体验未来城市吧~
  • 原文地址:https://blog.csdn.net/challenglistic/article/details/126355809