当前位置:   article > 正文

掘根宝典之C语言位字段

掘根宝典之C语言位字段

为什么要引入位字段

C语言引入位字段主要是为了节省内存空间和提高程序的执行效率。

  1. 节省内存空间:使用位字段可以将多个变量存储在同一个字节中,从而节省了内存空间。比如,如果一个变量只需要使用几个位来表示,而整型变量占据的空间通常是4个字节,这时可以使用位字段来存储,只占用所需的位数,从而减少了内存的使用量。

  2. 提高执行效率:位字段的操作是位级别的,所以在执行位操作时,可以直接操作整个字节而不需要进行位级别的操作。这样可以提高程序的执行效率,特别是对于一些需要频繁进行位运算的场景,如图形处理、网络协议解析等。

另外,位字段还提供了更灵活、更直观的数据表示方式,使得代码更易读、易懂。通过位字段,我们可以直接从代码中了解到不同位的含义和作用,而不需要通过位运算等方式来推测。

需要注意的是,位字段的使用需要谨慎,因为位字段的位数是有限的,超出位数范围会导致数据丢失或溢出。此外,位字段的布局和字节序是与具体的编译器和计算机体系结构相关的,可能在不同的平台上有差异。因此,在使用位字段时,需要确保对位数和字节序的理解和掌握。

什么是位字段

C语言中的位字段是一种数据结构,用来存储和操作以位为单位的数据。

它允许将几个不同变量压缩到同一个字节中,并且可以使用位操作来访问和修改这些变量的值。

位字段是一个signed int或unsigned int类型变量中的一组相邻的位(C99和C11新增了_Bool类型的位字段)。

位字段通常使用结构体来定义,在结构体中的每个成员都被声明为特定的位数。使用位字段可以有效地节省内存空间,特别是当需要存储多个布尔值或枚举类型时。

下面就是一个位字段

  1. struct Date {
  2. unsigned int day : 5; // 5位用于存储日
  3. unsigned int month : 4; // 4位用于存储月
  4. unsigned int year : 12; // 12位用于存储年
  5. };

声明位字段

在C语言中,可以使用冒号(:)后跟位数的方式来声明位字段。

位字段只能用于整数类型,如int、unsigned int、char,_Bool等。

以下是位字段的声明语法:

  1. struct <结构体的名称> {
  2. <位字段的数据类型> <位字段的名称> : <位字段所占位数>;
  3. ......
  4. };

注意后面是比特位,不是字节。int类型的不能超过4*8=32;char类型的不能超过1*8=8; 

下面是一个声明位字段的例子:

  1. #include <stdio.h>
  2. struct Date {
  3. unsigned int day : 5; // 5位用于存储日
  4. unsigned int month : 4; // 4位用于存储月
  5. unsigned int year : 12; // 12位用于存储年
  6. };
  7. int main() {
  8. struct Date d;
  9. d.day = 15;
  10. d.month = 10;
  11. d.year = 2021;
  12. printf("Date: %d/%d/%d\n", d.day, d.month, d.year);
  13. return 0;
  14. }

在上述例子中,我们声明了一个名为 Date 的结构体,并在结构体中定义了三个位字段 daymonthyear,分别占据了5位、4位和12位。通过使用位字段,我们可以有效地压缩日期数据,并且只占用2个字节的内存空间。

注意,位字段的位数是相对于位字段的数据类型而言的,所以需要确保位数不超过数据类型的位数范围。同时,位字段的位数必须是正整数。

用法

位字段的用法和结构体一模一样,我们就不多介绍了

含位字段的结构体的内存

内存分配规则

位字段(bit-fields)是一种特殊的结构体成员,用来指定结构体成员的位宽度。位字段的内存分配是根据编译器的实现规则来确定的。具体的内存分配规则可能会因编译器的实现而有所不同,以下是一些常见的规则:

  1. 位字段的起始位置默认是在当前字节的起始位置上。如果前面的成员已经占用了一部分字节,那么位字段从下一个可用的位位置开始。
  2. 如果位字段的宽度大于可用的位数,将会跨越到下一个字节。这也意味着在一个字节中可能有多个位字段。
  3. 如果位字段的宽度等于可用的位数,那么它将占满一个字节。
  4. 如果两个位字段相邻,并且宽度之和小于等于可用的位数,它们可能会被存储在同一个字节中。

下面是一个示例代码,展示了一个包含位字段的结构体及其可能的内存分配情况:

  1. #include <stdio.h>
  2. struct Flags {
  3. unsigned int flag1 : 1; // 1位
  4. unsigned int flag2 : 2; // 2位
  5. unsigned int flag3 : 3; // 3位
  6. };
  7. int main() {
  8. struct Flags f;
  9. printf("Sizeof struct Flags: %zu bytes\n", sizeof(f));
  10. // 输出每个位字段的地址
  11. printf("Address of flag1: %p\n", &f.flag1);
  12. printf("Address of flag2: %p\n", &f.flag2);
  13. printf("Address of flag3: %p\n", &f.flag3);
  14. return 0;
  15. }

输出结果可能是:

  1. Sizeof struct Flags: 4 bytes
  2. Address of flag1: 0x7fff5a0a4b27
  3. Address of flag2: 0x7fff5a0a4b27
  4. Address of flag3: 0x7fff5a0a4b27

这个示例中,整个结构体Flags的大小为4个字节。由于flag1占用了1位,flag2占用了2位,flag3占用了3位,因此它们可能会存储在同一个字节中,它们的地址是相同的。具体的内存分配可能会因编译器的实现而有所差异,所以在使用位字段时,最好参考相关的编译器文档和规范,以确保正确的行为。

例子1

  1. struct A
  2. {
  3. int a:2;
  4. int b:5;
  5. int c:10;
  6. int d:30;
  7. };

进入结构体,最先声明int类型,所以先分配4个字节,即32个比特位。a先占2个比特位,b占5个比特位,c占10个比特位,还剩15比特位。所以这15比特位不用了,这里会造成一点浪费的。发现不能存下d,又因为d为int类型,所以再开辟4个字节,即32个比特位,存下d。所以A类结构体的内存大小是8字节

例子2

  1. struct B
  2. {
  3. char a:3;
  4. char b:4;
  5. char c:5;
  6. char d:4;
  7. };

进入结构体,最先声明char类型,所以先分配1个字节,即8个比特位。a先占3个比特位,b占4个比特位,还剩1个比特位。c占5个比特位,不能存下c,所以剩下的1个比特位空着不用了,这里会造成一点浪费的。因为c是char类型,所以再开辟1个字节,即8个比特位。所以c先占有5个字节,还剩3个比特位,不能存下d,后面多余的3个字节就空着不用了,这里会造成一点浪费的。再另外开辟1个字节,存下d,剩余的4个比特位空着不用了,这里会造成一点浪费的,所以A类结构体的内存大小是3字节 

需要注意的是,位字段的具体内存分配和布局可能会因编译器和编译器选项的不同而有所差异,所以在编写具有位字段的结构体时,最好遵循相关的编译器文档和规范,以确保正确的行为。

位字段的规则

1. 位段的成员可以是 int, unsigned int, signed int 或者是 char,_Bool 等类型

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

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

位字段的跨平台问题

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

2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。

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

4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。

位段使⽤的注意事项

位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。

所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊ 放在⼀个变量中,然后赋值给位段的成员。

  1. struct A {
  2. int _a : 2;
  3. int _b : 5;
  4. int _c : 10;
  5. int _d : 30; };
  6. int main()
  7. {
  8. struct A sa = {0}; s
  9. canf("%d", &sa._b);//这是错误的
  10. //正确的⽰范
  11. int b = 0;
  12. scanf("%d", &b);
  13. sa._b = b;
  14. return 0;
  15. }

注意点

在使用位字段(bit-fields)时,有几个需要注意的点:

  1. 对位字段宽度的限制:位字段的宽度不能超过数据类型的位宽度。例如,如果使用的是unsigned int作为位字段的数据类型,那么位字段的宽度最多只能是32位。
  2. 位字段的存储顺序:位字段的存储顺序(即位字段在内存中的布局顺序)是由编译器决定的,这与平台、编译器以及编译选项有关。在不同的编译器和编译选项下,位字段的存储顺序可能不同。因此,在使用位字段时,不能假设它们的存储顺序是固定的。
  3. 意外的位字段溢出:由于位字段的宽度是固定的,当给位字段赋予超过其宽度的值时,可能会发生溢出。这将导致结果不可预测,因此在使用位字段时应注意赋值的范围和数据类型。
  4. 跨越字节边界时的对齐:当一个位字段跨越到下一个字节时,编译器可能会进行适当的对齐操作。对齐要求会因平台和编译器而不同,因此在使用位字段时,最好参考相关的编译器文档和规范,以确保正确的对齐行为。
  5. 位字段的可移植性:由于位字段的行为和存储是与编译器相关的,所以在不同的编译器和平台上,位字段的行为可能会有所不同。这意味着,使用位字段可能会导致不可移植的代码,因此,在编写可移植性高的代码时,最好避免使用位字段。

综上所述,尽管位字段在一些特定的场景下可以提供便利,但在使用时需要注意以上的限制和行为差异,以确保代码的可靠性和可移植性。

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

闽ICP备14008679号