C语言基础--位段类型
2009-10-09 16:47
176 查看
1.什么是位段
------------------------------------------------------------在前面各章中,
我们已经讨论过字节概念了。在大多数的计算机系统中,
一个字节是由八个更小的,
称作为位的单位组成的。位是比字节更小的单位。位只有两个值, 1
或 0
。因此,
存储在计算机存储器中的一个字节可以看成由八个二进制数字形成的串。
例如, 一个存放值 36 的字节是八个二进制数字的串: 可以表示成
00100100。 存入值24 的字节可以表示成 00010100。
有时, 我们希望不仅对字节进行操作, 也要能对位进行操作。例如,
用布尔真或假条件表示的标志, 在计算机中可用位来表示。
但是, 说明一个用作标志的普通变量至少要用一个字节---8 位,
而在某些计算机系统中则可能是 16 位。
如果我们想在一个很大的表中存储很多标志, 那么 "被浪费"
的内存空间是很可观的。在 C 语言中,
一种方法是用叫做位段的构造类型来定义一个压缩信息的结构。
什么是位段呢? 位段是 C 语言特有的数据结构,
它允许我们定义一个由位组成的段, 并可为它赋以一个名字。
![](http://its.nbtvu.net.cn/xhyu/cai_c/c_web/c/img/c831.jpg)
------------------------------------------------------------
2.位段的用法
------------------------------------------------------------我们已经了解什么是位段了, 现在我们继续讨论位段的使用方法。
先看一个例子: 我们需要用到五个变量。 假定, 其中三个用作标志,
称为 f1, f2 和 f3。
第四个称为 type, 取值范围为 1 至 12。 最后一个变量称为 index,
值的范围为 0 至 500。
通常, 我们用下面的语句来说明这些变量:
char f1,f2,f3;
unsigned int type;
unsigned int index;
但是, 实际上标志 f1, f2, f3 分别只需要 1 位。变量 type 只需要 4 位,
而变量 index 只需要 9 位。 总共是 16位 ---- 2
个字节。我们用两个字节就够了。
我们可这样来做:
struct packed_struct
{
unsigned int f1 :1;
unsigned int f2 :1;
unsigned int f3 :1;
unsigned int type :4;
unsigned int index :9;
};
![](http://its.nbtvu.net.cn/xhyu/cai_c/c_web/c/img/c832.jpg)
该例中, 我们定义了一个结构 packed_struct。该结构定义了五个成员。第一个成员叫做
f1, 是 unsigned int 类型的。紧跟在该成员名之后的 :1 规定了它以 1
位存放。类似地, 标志 f2 和 f3 被定义为长度只有 1 位的。定义成员
type 占有 4 位。定义成员 index 占有 9 位。C
编译器自动地把上面的位段定义压缩在一起。位段的划分如图所示。packed_struct
总共使用了 16 位。
这种方法的好处是, 定义成 packed_struct 类型的变量的位段,
可以如引用一般的结构成员一样方便地引用。同时,
使用了更少的内存单元数。
我们已经定义了一个称作为 packed_struct 的包含着位段的结构。现在,
我们象下面那样定义一个称作为 packet_data 的变量: struct packed_struct
packed_data; 于是, 我们就可以用简单的语句, 把 packed_data 的 type
位段设置为 7:
packed_data.type = 7;
类似地,
我们可以用下面的语句把这个位段的值设为 n:
packed_data.type = n;
我们不必担心 n 的值太长,
以致不能放入 type 位段中, C 编译器会自动地仅取出 n 的低四位,
把它赋值给 packed_data.type。取出位段的值也自动地处理的, 因此语句 n
= packed_data.type; 将从 packed_data 中取出 type 位段, 并把它的值赋给 n。
在一般的表达式中可以使用位段, 此时,
位段自动地转换成整数。因此, 表达式
i = packed_data.index/5+1;
是完全有效的。
在包含位段的结构中, 也可以包括 "通常的" 数据类型。因此,
如果我们想定义一个结构, 它包含一个 int, 一个 char, 和二个 1
位的标志, 那么, 下面的定义是有效的:
struct table_entry
{
int count ;
char c;
unsigned int f1 :1;
unsigned int f2 :1;
};
当位段出现在结构定义中时,
它们就被压缩成字。如果某个位段无法放入一个字中,
那么该字的剩余部分跳过不用, 该位段被放入下一个字中。
使用位段时, 必须注意下列事项:
在某些机器上, 位段总是作为 unsigned 处理,
而不管它们是否被说明成 unsigned 的。
大多数C 编译器都不支持超过一个字长的位段。
位段不可标明维数; 即, 不能说明位段数组, 例如 flag:l[2]。
最后, 不可以取位段地址。原因是, 在这种情况不, 显然没有称作为
"位段指针" 类型的变量。
这里, 我们再深入讨论一下位段。如果使用下面的结构定义:
![](http://its.nbtvu.net.cn/xhyu/cai_c/c_web/c/img/c833.jpg)
struct bits
{
unsigned int f1:1;
int word;
unsigned int f3:1;
};
那么, 位段是怎样压缩的呢? 由于成员 word 出现于其间, 故 f1, f3
不会压缩在同一个字内。C
编译器不会重新安排位段定义来试图优化存储空间。
可以指定无名位段, 使得一个字中的某些位被 "跳过"。因此,
定义:
![](http://its.nbtvu.net.cn/xhyu/cai_c/c_web/c/img/c834.jpg)
struct x_entry
{
unsigned int type :4;
unsigned int :3;
unsigned int count :9;
};
将定义一个结构 x_entry, 它包含两个位段变量 type 和 count,
而无名位段规定了 type 和 count 间隔三位。
------------------------------------------------------------
特别注意:
1)上面的有点地方有问题,就是字节顺序--其实很简单,处理器定义字节的前面3位是指该字节从右往左3位,而不是从左往右的3位,依次类推;
eg:
struct ABC{
unsigned int a:3;
unsigned int b:2;
unsigned int c:3;
};
struct ABC abc;
unsigned char *s = (unsigned char *)&abc;
*s = 0x99;//(10011001)2
--------------
|7|6|5|4|3|2|1|0|
--------------
| 10 0
| 11
|001
|
--------------
| c=4|b=3| a =1|
--------------
2)在某些机器上, 位段总是作为 unsigned 处理,
而不管它们是否被说明成 unsigned 的。但是有些机器就不一样!如果没有说明unsigned,
可能就当作有符号数处理;这样就很可能出现负数。
eg:
#include<stdio.h> typedef struct _ss_ { int a: 2; int b: 2; int c: 2; int d: 1; }ss; int main(void) { ss ff; ff.a=1; ff.b=2; ff.c=3; ff.d=14; printf("%d %d %d %d/n",ff.a,ff.b,ff.c,ff.d);//1 -2 -1 0 return 0; }
结果:1 -2 -1 0
------------------------
分析:
0 11 10 01 d c b a b是10因为是1打头,所以是负数` 10-1=01 取反,得10· 即为-2 按补码算的 同样c是11,得-1; d为一位,截断为最后一个位0;所有d是0;如果截断为最后一个位是1,那么d就是-1;(这个有点邪门?) ------------------------ 还是建议按照规范写上unsigned; ------------------------ 3) 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如: struct bs { unsigned a:4 unsigned :0 /*空域*/ unsigned b:4 /*从下一单元开始存放*/ unsigned c:4 } 在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。 ------------------------ 4)位域的长度不能大于一个int的长度,也就是说不能超过32位。 ------------------------ 5)位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如: struct k { int a:1 int :2 /*该2位不能使用*/ int b:3 int c:2 }; 从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的 6)位赋值的地方还是有点不明白?#include <stdio.h> int main() { struct ABC{ unsigned int a:3; unsigned int b:2; unsigned int c:3; }; struct ABC abc,bcd; unsigned char *s = (unsigned char *)&abc; *s = 0x99; bcd.a = abc.a; bcd.b = abc.b; bcd.c = abc.c; printf("%d/n",sizeof(struct ABC)); printf("%x/n",*s); printf("%x,%x,%x/n",abc.a,abc.b,abc.c); return 0; }0x08048374 <main+0>: lea 0x4(%esp),%ecx 0x08048378 <main+4>: and $0xfffffff0,%esp 0x0804837b <main+7>: pushl -0x4(%ecx) 0x0804837e <main+10>: push %ebp 0x0804837f <main+11>: mov %esp,%ebp 0x08048381 <main+13>: push %ecx 0x08048382 <main+14>: sub $0x24,%esp 0x08048385 <main+17>: lea -0x8(%ebp),%eax 0x08048388 <main+20>: mov %eax,-0x10(%ebp) 0x0804838b <main+23>: mov -0x10(%ebp),%eax 0x0804838e <main+26>: movb $0x99,(%eax) 0x08048391 <main+29>: movzbl -0x8(%ebp),%eax 0x08048395 <main+33>: and $0x7,%eax 0x08048398 <main+36>: mov %eax,%edx 0x0804839a <main+38>: and $0x7,%edx 0x0804839d <main+41>: movzbl -0xc(%ebp),%eax 0x080483a1 <main+45>: and $0xfffffff8,%eax 0x080483a4 <main+48>: or %edx,%eax 0x080483a6 <main+50>: mov %al,-0xc(%ebp) 0x080483a9 <main+53>: movzbl -0x8(%ebp),%eax 0x080483ad <main+57>: shr $0x3,%al 0x080483b0 <main+60>: and $0x3,%eax 0x080483b3 <main+63>: and $0x3,%eax 0x080483b6 <main+66>: lea 0x0(,%eax,8),%edx 0x080483bd <main+73>: movzbl -0xc(%ebp),%eax 0x080483c1 <main+77>: and $0xffffffe7,%eax 0x080483c4 <main+80>: or %edx,%eax 0x080483c6 <main+82>: mov %al,-0xc(%ebp) 0x080483c9 <main+85>: movzbl -0x8(%ebp),%eax 0x080483cd <main+89>: shr $0x5,%al 0x080483d0 <main+92>: mov %eax,%edx 0x080483d2 <main+94>: shl $0x5,%edx 0x080483d5 <main+97>: movzbl -0xc(%ebp),%eax 0x080483d9 <main+101>: and $0x1f,%eax 0x080483dc <main+104>: or %edx,%eax 0x080483de <main+106>: mov %al,-0xc(%ebp) 0x080483e1 <main+109>: movl $0x4,0x4(%esp) 0x080483e9 <main+117>: movl $0x8048520,(%esp) 0x080483f0 <main+124>: call 0x80482d8 <printf@plt> 0x080483f5 <main+129>: mov -0x10(%ebp),%eax 0x080483f8 <main+132>: movzbl (%eax),%eax 0x080483fb <main+135>: movzbl %al,%eax 0x080483fe <main+138>: mov %eax,0x4(%esp) 0x08048402 <main+142>: movl $0x8048524,(%esp) 0x08048409 <main+149>: call 0x80482d8 <printf@plt> 0x0804840e <main+154>: movzbl -0x8(%ebp),%eax 0x08048412 <main+158>: shr $0x5,%al 0x08048415 <main+161>: movzbl %al,%ecx 0x08048418 <main+164>: movzbl -0x8(%ebp),%eax 0x0804841c <main+168>: shr $0x3,%al 0x0804841f <main+171>: and $0x3,%eax 0x08048422 <main+174>: movzbl %al,%edx 0x08048425 <main+177>: movzbl -0x8(%ebp),%eax 0x08048429 <main+181>: and $0x7,%eax 0x0804842c <main+184>: movzbl %al,%eax 0x0804842f <main+187>: mov %ecx,0xc(%esp) 0x08048433 <main+191>: mov %edx,0x8(%esp) 0x08048437 <main+195>: mov %eax,0x4(%esp) 0x0804843b <main+199>: movl $0x8048528,(%esp) 0x08048442 <main+206>: call 0x80482d8 <printf@plt> 0x08048447 <main+211>: mov $0x0,%eax 0x0804844c <main+216>: add $0x24,%esp 0x0804844f <main+219>: pop %ecx 0x08048450 <main+220>: pop %ebp 0x08048451 <main+221>: lea -0x4(%ecx),%esp 0x08048454 <main+224>: ret
--------------
0x080483ad <main+57>: shr $0x3,%al
0x080483b0 <main+60>: and $0x3,%eax
0x080483b3 <main+63>: and $0x3,%eax
为什么两次AND
------------------------------------------------------------
相关文章推荐
- 20161211C语言基础03_整数类型
- c语言基础:各种数据类型的输出占位符
- 20161212C语言基础04_char类型
- Go语言基础入门--变量,类型
- Java笔记之语言基础_基本数据类型
- Java语言基础{Java_se(02)}-字面量和常量-变量的基本定义和使用-Java表达式-数据类型和分类-基本数据类型的转换-算数运算符-赋值运算符-比较运算符-三元运算符-逻辑运算符
- Go语言学习二:Go基础(变量、常量、数值类型、字符串、错误类型)
- Python语言基础学习(数据类型,迭代器及生成器)
- Java语言基础:基本数组类型
- R语言基础入门2--基本数据类型
- Java语言基础-2.八种基本数据类型
- 自动化(Automation)基础概念:接口描述语言(IDL)与类型库(TypeLib)
- C语言基础:基本数据类型
- 【Java语言基础】数据类型详解
- C语言基础:C语言变量类型 - 全局变量和局部变量
- 02.java语言基础-参数的值传递机制-基本数据类型
- [Java入门笔记] Java语言基础(二):常量、变量与数据类型
- 【C语言基础】-01数据类型
- C语言基础学习基本数据类型-其他整数类型
- Javascript 语言基础2(类型转换、函数)