赞
踩
位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。(来自:百度百科-位段)
主要介绍位段与结构体声明的不同
1.位段的成员必须是int、char、unsigned int、unsigned char
2.位段的成员名后有一个冒号和一个数字
位段声明示例
struct S
{
unsigned char a:2;
unsigned char b:5;
};
位段成员名后的数字表示每个成员变量占据的空间,单位是位,比如该例中的 a 是两位,b 是5位,此时的两个变量总共占据了不到一个字节的空间。
我们可以通过计算S的大小来确定使用位段是否能够节省内存。
int main()
{
printf("struct S: %zd\n", sizeof(struct S));
printf("unsigned char: %zd\n", sizeof(unsigned char));
return 0;
}
sizeof 操作符计算的是数据类型占据内存的大小,单位是字节,可以看出,位段的确可以节省内存。
#include<stdio.h> struct S { char a : 4; char b : 5; char c : 3; char d : 4; }; int main() { struct S temp = { 0 }; printf("temp : %d\n", sizeof(temp)); temp.a = 10; temp.b = 15; temp.c = 7; temp.d = 3; return 0; }
temp的大小为3字节,但是a,b,c,d的位数加起来是16位,也就是2个字节,这说明位段并不是直接连续存储在内存中的。
可以通过调试对temp在内存中的存储进行进一步的观察。
首先(Fn+)F10,进行调试,在创建temp变量之后就可以打开调试->窗口->内存->内存1,然后在地址那里输入&temp,就可以观察temp变量的变化了。
继续(Fn+)F10,可以不断观察变化。在箭头指向temp.c的赋值语句时,temp.a的值是10,在内存中存储的是二进制,在VS这里是以16进制的方式显示的,也就是0a,temp.b的值是15,16进制就是0f,那个黄色的箭头代表将要执行的语句,也就是说此时的temp.c和temp.d还未赋值。
再(Fn+)F10,可以看到 0f 变成了 ef ,也就是说temp.b和temp.c是共用一个字节的内存。
temp.b : 0f(16进制) 00001111(2进制)
这个字节的大小变为ef,也就是11101111,而temp.c的大小刚好是7也就是111(2进制),temp.b是5位,temp.c是3位。temp.c也就是temp.b所在的这一个字节的前面三位。
继续(Fn+)F10,03又是另外一个字节的内容了。
从这个例子中我们可以了解到位段存储的一些规则。
一个位段必须存储在一个存储单元内,不能跨两个单元,如果第一个单元不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段,对于这个存储单元,char类型的是1个字节,int类型是4个字节
为什么temp.a是0a不是a0呢?为什么是fe不是ef?
其实这个问题和大小端相似,可以对照着理解。
#include<stdio.h> struct S { char a : 4; char b : 5; char c : 3; char d : 4; char e : 5; }; int main() { struct S temp = { 0 }; printf("temp : %zd\n", sizeof(temp)); temp.a = 10;//0a temp.b = 15;//1110 1111 temp.c = 7;// temp.d = 3;//03 temp.e = 0x11; printf("%x\n", temp);//VS出现警告 return 0; }
该代码在S的定义中增加了语句 char e: 5; 让temp的大小达到4字节,以%x的形式打印temp.
输出结果中11是我们新加的代码temp.e = 0x11; 03ef0a是我们上面分析的代码,但是和我们在内存中看到的字节的顺序是相反的。
这里需要补充大小端的内容。
大端存储:数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中
小端存储:数据的高位保存在内存的高地址中,数据的高位保存在内存的低地址中
以数字 0x12 为例,其中的 2 就是低位,1就是高位,计算机在存储的时候可以选择将 1 存储在低地址,将 2 存储在高地址,这种就是大端存储;如果将 1 存储在高地址,将 2 存储在低地址,就是小端存储。
我们再次进行调试,并将内存窗口的列数改为1
通过调试,可以看到0a的地址最小,在我们的输出结果1103ef0a中,0a就是低位,表明了该电脑的存储方式是小端存储。
对于temp中的 b 和 c 来说,temp.b占了5位,temp.c占了3位,在使用temp.b的时候,我们只会使用temp.b的5个字节的内容,而在使用temp.c的时候,只会拿temp.c所在的3个字节的内容,那个变量在前或者在后对我们的使用没有影响。通过这个例子,可以得出结构体中在小端存储的电脑中,如果有多个变量共用一个字节,结构体中先定义的位段变量会存放在字节的低位,就像是temp.a在内存中是0a而不是a0一样。
对于大端存储的电脑,与小端存储的相反。
那么如果我们交换b和c的顺序,会出现fe吗?
#include<stdio.h> struct S { char a : 4; char c : 3; char b : 5; char d : 4; char e : 5; }; int main() { struct S temp = { 0 }; temp.a = 10;//0a temp.b = 15;//1110 1111 temp.c = 7;// temp.d = 3;//03 temp.e = 0x11; return 0; }
在结构体S中 a 和 c 是相邻的两个变量,a占4位,c占3位,temp.a和temp.c共用同一字节,因为结构体中先定义的a在字节的低位(小端存储),后4位:temp.a : 10 : 1010 (二进制): a(十六进制) ; 前4位中的后3位,temp.c : 7 : 111(二进制) : 7(十六进制),所以是7a。因为在刚开始的时候temp就被赋值成了0,所以前4位中的第一位是0。
如果此时再将a和c的位置交换一下结果会是什么呢?会出现a7吗?
#include<stdio.h> struct S { char c : 3; char a : 4; char b : 5; char d : 4; char e : 5; }; int main() { struct S temp = { 0 }; temp.a = 10;//0a temp.b = 15;//1110 1111 temp.c = 7;// temp.d = 3;//03 temp.e = 0x11; return 0; }
此时c是结构体中先定义的,所以第一个字节的后3位是c,也就是7:111(二进制),c变量前面的4位是a,就是10:1010(二进制),因为开始时,temp被初始化为0,该字节第一位没被赋值,保持不变,仍然是0,所以该字节的内容是01010111,就是57(16进制)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。