1. 主页
  2. 文档
  3. C语言教程
  4. C语言枚举、结构和联合
  5. C中的位域

C中的位域

在 C 中,我们可以指定结构和联合成员的大小(以位为单位)。这个想法是当我们知道一个字段或一组字段的值永远不会超过限制或在一个小范围内时有效地使用内存。 
例如,考虑以下不使用位字段的日期声明。

#include <stdio.h>
// A simple representation of the date
struct date {
unsigned int d;
unsigned int m;
unsigned int y;
};

int main()
{
printf("Size of date is %lu bytes\n",
sizeof(struct date));
struct date dt = { 31, 12, 2014 };
printf("Date is %d/%d/%d", dt.d, dt.m, dt.y);
}

输出: 

Size of date is 12 bytes
Date is 31/12/2014

上面的“日期”表示在编译器上占用 12 个字节,而 unsigned int 占用 4 个字节。由于我们知道 d 的值总是从 1 到 31,m 的值是从 1 到 12,我们可以使用位域来优化空间。

但是,如果使用signed int 编写相同的代码并且字段的值超出分配给变量的位并且可能会发生一些有趣的事情。例如,考虑相同的代码,但使用有符号整数:

#include <stdio.h>

// Space optimized representation of the date
struct date {
// d has value between 0 and 31, so 5 bits
// are sufficient
int d : 5;

// m has value between 0 and 15, so 4 bits
// are sufficient
int m : 4;

int y;
};

int main()
{
printf("Size of date is %lu bytes\n",
sizeof(struct date));
struct date dt = { 31, 12, 2014 };
printf("Date is %d/%d/%d", dt.d, dt.m, dt.y);
return 0;
}

输出: 

Size of date is 8 bytes
Date is -1/-4/2014

输出结果为负。后面发生的是值 31 存储在等于 11111 的 5 位有符号整数中。MSB 是 1,所以它是一个负数,你需要计算二进制数的 2 的补码才能得到它的实际值是内部完成的。通过计算 2 的补码,您将得到值 00001,它相当于十进制数 1,因为它是负数,所以您得到 -1。类似的事情发生在 12 上,在这种情况下你得到 1100 的 4 位表示,在计算 2 的补码时你得到 -4 的值。

以下是关于 C 中位域的一些有趣事实。
1)使用大小为 0 的特殊未命名位域来强制在下一个边界对齐。例如考虑以下程序。 


#include <stdio.h>

// A structure without forced alignment
struct test1 {
unsigned int x : 5;
unsigned int y : 8;
};

// A structure with forced alignment
struct test2 {
unsigned int x : 5;
unsigned int : 0;
unsigned int y : 8;
};

int main()
{
printf("Size of test1 is %lu bytes\n",
sizeof(struct test1));
printf("Size of test2 is %lu bytes\n",
sizeof(struct test2));
return 0;
}

输出: 

Size of test1 is 4 bytes
Size of test2 is 8 bytes

2)我们不能有指向位域成员的指针,因为它们可能不是从字节边界开始的。 

#include <stdio.h>
struct test {
unsigned int x : 5;
unsigned int y : 5;
unsigned int z;
};
int main()
{
struct test t;

// Uncommenting the following line will make
// the program compile and run
printf("Address of t.x is %p", &t.x);

// The below line works fine as z is not a
// bit field member
printf("Address of t.z is %p", &t.z);
return 0;
}

输出: 

prog.c: In function 'main':
prog.c:14:1: error: cannot take address of bit-field 'x'
 printf("Address of t.x is %p", &t.x); 
 ^

 3)将超出范围的值分配给位字段成员是实现定义的。 

#include <stdio.h>
struct test {
unsigned int x : 2;
unsigned int y : 2;
unsigned int z : 2;
};
int main()
{
struct test t;
t.x = 5;
printf("%d", t.x);
return 0;
}

输出: 

Implementation-Dependent

4)在 C++ 中,我们可以在结构/类中拥有静态成员,但位域不能是静态的。 

// The below C++ program compiles and runs fine
struct test1 {
static unsigned int x;
};
int main() {}
// But below C++ program fails in the compilation
// as bit fields cannot be static
struct test1 {
static unsigned int x : 5;
};
int main() {}

输出: 

prog.cpp:5:29: error: static member 'x' cannot be a bit-field
     static unsigned int x : 5;
                             ^

5)位域数组是不允许的。例如,下面的程序编译失败。 


struct test {
unsigned int x[10] : 5;
};

int main()
{
}

输出: 

prog.c:3:1: error: bit-field 'x' has invalid type
 unsigned int x[10]: 5; 
 ^

练习: 
预测下列程序的输出。假设 unsigned int 占用 4 个字节,long int 占用 8 个字节。 
1) 

#include <stdio.h>
struct test {
unsigned int x;
unsigned int y : 33;
unsigned int z;
};
int main()
{
printf("%lu", sizeof(struct test));
return 0;
}

2) 

#include <stdio.h>
struct test {
unsigned int x;
long int y : 33;
unsigned int z;
};
int main()
{
struct test t;
unsigned int* ptr1 = &t.x;
unsigned int* ptr2 = &t.z;
printf("%d", ptr2 - ptr1);
return 0;
}

3) 

union test {
unsigned int x : 3;
unsigned int y : 3;
int z;
};

int main()
{
union test t;
t.x = 5;
t.y = 4;
t.z = 1;
printf("t.x = %d, t.y = %d, t.z = %d",
t.x, t.y, t.z);
return 0;
}

4)使用 C 中的位域来确定机器是 little-endian 还是 big-endian。
应用 – 

  • 如果存储空间有限,我们可以选择位域。
  • 当设备针对这种情况传输编码为多个位的状态或信息时,位域是最有效的。
  • 加密例程需要访问字节内的位,在这种情况下位域非常有用。
这篇文章对您有用吗?