在C语言中,我们可以指定结构和联合成员的大小(以位为单位)。这个想法是当我们知道一个字段或一组字段的值永远不会超过限制或范围很小时,有效地使用内存。
例如,考虑以下日期声明而不使用位字段。
C
#include
// 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);
}
C
#include
// 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;
}
C
#include
// 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;
}
C
#include
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;
}
C
#include
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;
}
CPP
// The below C++ program compiles and runs fine
struct test1 {
static unsigned int x;
};
int main() {}
CPP
// But below C++ program fails in the compilation
// as bit fields cannot be static
struct test1 {
static unsigned int x : 5;
};
int main() {}
C
struct test {
unsigned int x[10] : 5;
};
int main()
{
}
CPP
#include
struct test {
unsigned int x;
unsigned int y : 33;
unsigned int z;
};
int main()
{
printf("%lu", sizeof(struct test));
return 0;
}
CPP
#include
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;
}
CPP
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;
}
Size of date is 12 bytes
Date is 31/12/2014
上面的’date’表示形式在编译器上占用12个字节,而无符号int占用4个字节。因为我们知道d的值总是从1到31,所以m的值是从1到12,所以我们可以使用位字段来优化空间。
Size of date is 8 bytes
Date is 31/12/2014
但是,如果使用有符号的int编写相同的代码,并且字段的值超出了分配给变量的位,则可能会发生一些有趣的事情。例如,考虑相同的代码,但带符号的整数:
C
#include
// 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以5位带符号整数存储,该整数等于11111。MSB为1,因此它是负数,您需要计算二进制数的2的补码以获取其实际值,即是内部完成的。通过计算2的补数,您将得出值00001,该值等于十进制数字1,并且由于它是负数,您得到-1。类似的事情发生在12上,在这种情况下,您得到4位表示为1100,在计算2的补码时,得到的值是-4。
以下是有关C中位字段的一些有趣事实。
1)使用大小为0的特殊未命名位字段来强制在下一个边界上对齐。例如,考虑以下程序。
C
#include
// 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)我们不能有指向位字段成员的指针,因为它们可能不是从字节边界开始的。
C
#include
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)定义为将超出范围的值分配给位字段成员。
C
#include
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++中,我们可以在结构/类中具有静态成员,但是位字段不能为静态。
CPP
// The below C++ program compiles and runs fine
struct test1 {
static unsigned int x;
};
int main() {}
CPP
// 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)不允许使用位字段数组。例如,下面的程序编译失败。
C
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)
CPP
#include
struct test {
unsigned int x;
unsigned int y : 33;
unsigned int z;
};
int main()
{
printf("%lu", sizeof(struct test));
return 0;
}
2)
CPP
#include
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)
CPP
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中的位字段来确定机器是小端还是大端的方式。
应用–
- 如果存储空间有限,我们可以使用位域。
- 当设备针对这种情况传输状态或编码成多个比特的信息时,比特字段是最有效的。
- 在那种情况下,位域非常有用,因此加密例程需要访问一个字节中的位。