位域
来源:互联网 发布:pwp网络用语是什么意思 编辑:程序博客网 时间:2024/05/21 09:00
1. 位域的定义
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:
位结构定义的一般形式为:
struct 位结构名{
数据类型 [变量名]:整型常数; //成员称为“位域”或者“位段”
数据类型 [变量名]:整型常数;
} 位结构变量;
其中:数据类型必须是整型(包括字符型)。整型常数的范围是数据类型的长度,如定义为short,则范围是1~16,也就是说,当定义一个short类型的成员位段时,它的整形常数取值范围必须在0-16之间,超出范围编译器将报语法错。具体范围如下:
类型
整形常数范围
char, unsigned char
1-8
short, unsigned short
1-16
int, unsigned
1-32
long, unsigned long
1-32
当然上面的范围值也可以是0,但是作用不同,后面会提到。
例如:
struct webpage{
unsigned char incon: 8; // incon占用低字节的0~7共8位
unsigned char txcolor: 4; // txcolor占用高字节的0~3位共4位
unsigned char bgcolor: 3; // bgcolor占用高字节的4~6位共3位
unsigned char blink: 1; // blink占用高字节的第7位
}ch;
printf("%d\n", sizeof(struct webpage));
输出结果:2。
2. 位域的对齐与计算
上面结构体大小为2,但是,为什么会是2呢?先来了解下,结构体的对齐原则:
1). 若相邻成员变量类型相同,且其位宽之和不大于成员变量类型位宽(在此严重强调,是类型位宽而不是成员变量sizeof,也不是类型的sizeof或者其他什么)大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2). 如果相邻位域字段的类型相同,但其位宽之和不大于成员变量的类型宽度大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3). 如果相邻的位域字段的类型不同,不同位域字段存放在不同的位域类型字节中(这一条其实会根据编译器的不同而采用不同的规则)。
例如:
struct A
{
char c1:1;
char c2:3;
unsigned short s2:13;
unsigned long i:3;
};
printf(“%d\n”, sizeof(struct A));
我们先分析下,如果遵循上面的对齐原则,结果应该会是多少。
c1, c2符合原则2,类型相同,那么就是 c1占1位,在0上;c2占3位在1~3上,因为后面的unsigned short与c1, c2不是同类型,符合原则3,所以存储在新的字节上。即c1, c2在一个字节上; s2占13位,在0-12上,也因和后面的i不是同类型,所以存储在新字节上,即s2占两个字节。i占3位,在0-2上,占四个字节。
即:1 + 2 + 4 = 7,但实际上,结构体还有个原则要遵守,即:和结构体一样,位结构体也是按照成员的最大长度字节来对齐分配空间的。如顺序读到的位域中最长的那个字节数对齐,如为4字节的long,则按4字节对齐。若最长仅为char,则按char对齐。即依次按读取到的数据类型进行对齐,并计算出最后的sizeof()!所以,这里最大的成员类型是4个字节,结构体的大小会是它的倍数,所以为距7最小4的倍数是8。得出,上面位结构体大小为:8,VC 6.0下输出结果也为8。
但是,在Code::Blocks 10.5中,输出结果却为4。也就是说,它并没有遵循上面的原则3。所以,原则3会和具体的编译环境有关。
如果位域上的整形范围值是0,则下个位域从新的字节开始(即使是同类型的位域),如:
struct bs
{
unsigned a:4;
unsigned :0; // 空域
unsigned b:4; // 从新字节开始存放
unsigned c:4;
} ;
上面这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从新的字节开始,占用4位,c占用4位。上面位结构体大小为:8
3. 位域的访问
位结构成员的访问与结构成员的访问相同。例如:访问下例位结构中的bgcolor成员可写成ch.bgcolor进行访问:
struct webpage{
unsigned char incon: 8; // incon占用低字节的0~7共8位
unsigned char txcolor: 4; // txcolor占用高字节的0~3位共4位
unsigned char bgcolor: 3; // bgcolor占用高字节的4~6位共3位
unsigned char blink: 1; // blink占用高字节的第7位
}ch;
位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k
{
int a:1;
int :2; // 该2位不能使用
int b:3;
int c:2;
};
注意:
1. 一个位域必须存储在定义它的一个数据类型内部,不能跨跨该数据类型。如char定义的位域所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
2.由于位域不允许越过定义它的数据类型,因此位域的长度不能大于定义它的数据类型的长度。
3. 位结构总长度(位数),是各个位成员定义的位数之和再向最大结构成员对齐。
4. 位结构成员可以与其它结构成员一起使用。
例如:
struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned char state: 1;
unsigned char pay: 1;
}workers;
上例的结构定义了关于一个工人的信息。其中有两个位结构成员,每个位结构成员只有一位,用unsigned char数据类型,因此只占一个字节但保存了两个信息,该字节中第一位表示工人的状态,第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。
这里有篇文章也写的不错http://blog.csdn.net/juliababy/article/details/2873796
=====================================================================================
一、首先说概念:
位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构比按位运算符更加方便。
位结构定义的一般形式为:
struct 位结构名{
数据类型 [变量名]: 整型常数; //成员称为“位域”或者“位段”
数据类型 [变量名]: 整型常数;
} 位结构变量;
其中: 数据类型必须是整型(int/char/short)。 整型常数的范围是数据类型的长度, 如定义为short,则范围是1~16。
变量名是选择项, 可以不命名, 这样规定是为了排列需要。
例如: 下面定义了一个位结构。
struct webpage{
unsigned char incon: 8; /*incon占用低字节的0~7共8位*/
unsigned char txcolor: 4;/*txcolor占用高字节的0~3位共4位*/
unsigned char bgcolor: 3;/*bgcolor占用高字节的4~6位共3位*/
unsigned char blink: 1; /*blink占用高字节的第7位*/
}ch;
printf("%d/n",sizeof(struct webpage));输出:2。
位结构成员的访问与结构成员的访问相同。
例如: 访问上例位结构中的bgcolor成员可写成:
ch.bgcolor
注意:
1. 一个位域必须存储在定义它的一个数据类型内部,不能跨跨该数据类型。如char定义的位域所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
2.由于位域不允许越过定义它的数据类型,因此位域的长度不能大于定义它的数据类型的长度。
3. 位结构总长度(位数), 是各个位成员定义的位数之和再向最大结构成员对齐。
4. 位结构成员可以与其它结构成员一起使用。
例如:
struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned char state: 1;
unsigned char pay: 1;
}workers;
上例的结构定义了关于一个工人的信息。其中有两个位结构成员, 每个位结构成员只有一位, 用unsigned char数据类型,因此只占一个字节但保存了两个信息, 该字节中第一位表示工人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。
二、再说说位结构的位域存储顺序问题
我们知道字节存储顺序有高字节优先的big-endian大端存储法(高字节数据放在低字节地址处)和低字节优先的little-endian小端存储法,无论使用大端法还是小端法,都不存在技术原因,只是涉及到处理器厂商的立场和习惯。INTEL的X86平台使用小端法,IBM、Motorola、Sun Microsystem的大多数微处理器则使用大端法,还有部分微处理器可以由用户自己设置是使用大端法还是小端法,如ARM、MIPS、PowerPC等。
位域在存储时的顺序和它的编译器有关,一般是先申请的放在低位。程序举例如下:
小端???
void main()
{
union
{
struct student
{
unsigned char s1:1;
unsigned char s2:3;
}x;
unsigned char c;
}v;
v.c=0;
v.x.s1=0;
v.x.s2=4;
printf("%d/n",v.c);
printf("%d/n",sizeof(struct student));
}
输出:
8
1
即结构体成员申请是按照顺序从低地址开始。所以上边结构体v在内存中数据的排列顺序为
s1 s2
|0| 0|0|1| 0|0|0|0| (1个字节,因为是unsigned char类型)
低地址 高地址
s1放着0
s2放着4(二进制100),在内存里由低到高为“|0|0|1|”。
所以v.c为二进制00001000,即十进制8。
同时,因为s1占一个位,s2占三个位,而两者都是unsigned char型,且最大的数据类型也就是unsigned char型,一个字节足够放下s1和s2了。所以我们看到struct student的大小为1个字节。
如果从先申请的放在高字节,则上边的输出为
s2 s1
0000 |001 |0
即输出应该是:
64
1
网上有人说TURBO C是采用这种方式,我没试过。
三、位结构的位对齐问题
位结构的其实不存在位对齐问题,即位不需要对齐。其他方面,位结构和一般结构体类似,遵循结构体的对齐原则,
void main()
{
union
{
struct student
{
unsigned char s1:1;
unsigned char s2:2;
unsigned char s3:2;
}x;
unsigned char c;
}v;
v.c=0;
v.x.s1=0;
v.x.s3=2;
printf("%d/n",v.c);
printf("%d/n",sizeof(struct student));
}
输出结果是:
16
1
因为它只按整体对齐,所以为
s1s2s3
0 0001000
即二进制00010000等于十进制16,而不是
s1s2 s3
0 00 0 01 00
再举一个位结构体的例子
void main()
{
union
{
struct student
{
unsigned char s1:1;
unsigned char s2:2;
unsigned short s3:2;
}x;
unsigned short c;
unsigned int d;
}v;
v.d=0;
v.x.s1=0;
v.x.s3=2;
printf("%d/n",v.d);
printf("%d/n",sizeof(struct student));
}
输出为:
131072
4
131072=(10 00000000 00000000)b
因为遵循结构体对齐原则,s3跳过了2个字节。
s1s2 s3
0 00 00000| 00000000| 01
如下代码
struct kk
{
unsigned a:2;
unsigned b:3;
unsigned c:2;
unsigned d:1;
}
kt;
int main()
{
char result =3;
memcpy(&kt,&result,1);
printf( " a = %d, b = %d, c = %d, d = %d,ok/n" ,kt.a,kt.b,kt.c,kt.d);
return 1;
}
在sun unix上用cc编译,结果为0,0,1,1
在2000上用VC6.0编译,结果为3,0,0,0
struct kk {
char a:2;
char b:3;
char c:2;
char d:1;
} kt;
int main()
{
char result = 3;
memcpy(&kt, &result, 1);
printf("a = %d, b = %d, c = %d, d = %d/n",kt.a,kt.b,kt.c,kt.d);
return 1;
}
file bit
bit: ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), dynamically linked (uses shared libs),for GNU/Linux 2.6.0, not stripped
./bit
a = 0, b = 0, c = 1, d = 1
$ file bit
bit: PE32 executable for MS Windows (console) Intel 80386 32-bit
$ ./bit
a = -1, b = 0, c = 0, d = 0
=================================================
C语言的位域虽然很多人强烈建议不要使用,但现有系统里还广泛存在位域的使用,所以还是很有必要理清楚的。
对big-endian和little-endian的区别,很多人认为是对多字节数据类型而言。其实,问题的本质不在这里。
两种endian区别的本质是由于CPU的数据引脚和系统地址总线的连接方向的不同。也就是说,高地址,低地址的区别不仅体现在“字节序”上,还体现在“比特序”上,只不过因为系统屏蔽了“比特序”的一些细节,所以看起来问题仅仅是字节之间的顺序问题了。
所以,对字节序相关的问题,如果能从两个角度来看问题,就很直接明了了。
先从系统地址总线的角度来分析数据的存放,然后从CPU的数据引脚的角度来解析数据....(对网络字节,可从网络数据传送地址的角度分析数据的存放,思想类同)
废话少说,分析一下上面的例子程序。(所有图表都从系统地址总线的角度看各个bit位)
C语言的位域中,每个field是严格按照bit地址从低到高排列的:
因此,从地址总线的角度看,四个域的排列顺序是(用于解析)
==============
-低->->->高-
a b c d
==============
接下来看如何解析这个字节。
在little-endian机器上,result字节中各个比特的存放排列如下所示
==============
-低->->->高-
,11000000,
==============
对应到四个位域,得
a=3
b=0
c=0
d=0
在big-endian机器上,result字节中各个比特的存放排列如下所示
==============
-低->->->高-
,00000011,
==============
对应到四个位域,得
a=0
b=0
c=1
d=1
总结:
1. C语言的位域类型使用时,各个field的摆放是按从低到高的bit顺序排列的。
2. 把数据的存放和解析分开,就可以很容易解释字节序的问题。
是这样的。这里有个术语的混淆。当多个字节组成一个长字(long)时,不同体系结构的cpu会按不同的方法来解释高低字节的顺序,这是字节序。小端的cpu会将低地址的字节认为是长字值的低位,高地址的字节认为是长字值得高位。大端的正好相反。
而在一个字节之内,不同体系结构的cpu会按不同的方法来解释高低比特的顺序,这是比特序。 小端的cpu将低位的比特认为是值得低位,高位的比特认为是值得高位。大端的正好相反。假设固定总线上的比特地址,从0比特到7比特的内存数据是 0 0 0 0 0 0 0 1。那么,小端的cpu读到的值就是0x80,而大端的cpu读到的值就是0x01。
INTEL的cpu字节序和比特序都是小端的。POWERPC的cpu一般都是大端的.
明白以上道理,你可以分析IP header了.
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
/*The options start here. */
};
以及 tcp header
struct tcphdr
{
u_int16_t source;
u_int16_t dest;
u_int32_t seq;
u_int32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
# else
# error "Adjust your <bits/endian.h> defines"
# endif
u_int16_t window;
u_int16_t check;
u_int16_t urg_ptr;
};
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- 位域
- USACO Cowtour
- stl(一)------如何遍历map中的元素,以及如何在map中插入元素
- 细嚼慢咽C++primer(5)——顺序容器
- HP-UX磁带备份错误收集
- 入门视频采集与处理(BT656简介)
- 位域
- Leetcode Recover Binary Search Tree
- 【D3.js数据可视化系列教程】--(十五)SVG基本图形绘制
- 如何使用Discuz!后台备份和恢复Discuz!站点数据库
- Flash Builder的代码着色
- WordPress完美备份数据方法及教程
- Android XML 解析
- Struts2常量详解
- 童言童语