例如, 一个存放值 36 的字节是八个二进制数字的串: 可以表示成 00100100。 存入值24 的字节可以表示成 00010100。
有时, 我们希望不仅对字节进行操作, 也要能对位进行操作。例如, 用布尔真或假条件表示的标志, 在计算机中可用位来表示。
但是, 说明一个用作标志的普通变量至少要用一个字节---8 位, 而在某些计算机系统中则可能是 16 位。如果我们想在一个很大的表中存储很多标志, 那么 "被浪费" 的内存空间是很可观的。在 C 语言中, 一种方法是用叫做位段的构造类型来定义一个压缩信息的结构。
什么是位段呢? 位段是 C 语言特有的数据结构, 它允许我们定义一个由位组成的段, 并可为它赋以一个名字。
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;
};
这种方法的好处是, 定义成 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]。
- 最后, 不可以取位段地址。原因是, 在这种情况不, 显然没有称作为 "位段指针" 类型的变量。
struct bits
{
unsigned int f1:1;
int word;
unsigned int f3:1;
};
那么, 位段是怎样压缩的呢? 由于成员 word 出现于其间, 故 f1, f3 不会压缩在同一个字内。C 编译器不会重新安排位段定义来试图优化存储空间。
可以指定无名位段, 使得一个字中的某些位被 "跳过"。因此, 定义:
struct x_entry
{
unsigned int type :4;
unsigned int :3;
unsigned int count :9;
};
位段以位为单位定义结构体(或共用体)中成员所占存储空间的长度。
含有位段的结构体类型称为位段结构。
位段结构也是一种结构体类型,只不过其中含有以位为单位定义存储长度的整数类型位段成员。采用位段结构既节省存储空间,又可方便操作。
位段结构中位段的定义格式为:
unsigned <成员名>:<二进制位数>
例如:
struct bytedata
{unsigned a:2; /*位段a,占2位*/
unsigned:6; /*无名位段,占6位,但不能访问*/
unsigned:0; /*无名位段,占0位,表下一位段从下一字边界开始*/
unsigned b:10; /*位段b,占10位*/
int i; /*成员i,从下一字边界开始*/
}data;
位段数据的引用:
同结构体成员中的数据引用一样,但应注意位段的最大取值范围不要超出二进制位数定的范围,否则超出部分会丢弃。
例如:data.a=2; 但 data.a=10;就超出范围(a占2位,最大3)
关于位段数据,注意以下几点:
(1)一个位段必须存储在同一存储单元(即字)之中,不能跨两个单元。如果其单元空间不够,则剩余空间不用,从下一个单元起存放该位段。
(2)可以通过定义长度为0的位段的方式使下一位段从下一存储单元开始。
(3)可以定义无名位段。
(4)位段的长度不能大于存储单元的长度。
(5)位段无地址,不能对位段进行取地址运算。
(6)位段可以以%d,%o,%x格式输出。
(7)位段若出现在表达式中,将被系统自动转换成整数。
C语言中的结构是有实现位段的能力的,噢!你问它到底是什么形式是吧?这个问题呆会给你答案。让我们先看看位段的作用:位段是在字段的声明后面加一个冒号以及一个表示字段位长的整数来实现的。这种用法又被就叫作“深入逻辑元件的编程”,如果你对系统编程感兴趣,那么这篇文章你就不应该错过! 我把使用位段的几个理由告诉大家:1、它能把长度为奇数的数据包装在一起,从而节省存储的空间;2、它可以很方便地访问一个整型值的部分内容。 首先我要提醒大家注意几点:1、位段成员只有三种类型:int ,unsigned int 和signed int这三种(当然了,int型位段是不是可以取负数不是我说了算的,因为这是和你的编译器来决定的。位段,位段,它是用来表示字段位长(bit)的,它只有整型值,不会有7.2这种float类型的,如果你说有,那你就等于承认了有7.2个人这个概念,当然也没有char这个类型的);2、成员名后面的一个冒号和一个整数,这个整数指定该位段的位长(bit);3、许多编译器把位段成员的字长限制在一个int的长度范围之内;4、位段成员在内存的实现是从左到右还是从右到左是由编译器来决定的,但二者皆对。 下面我们就来看看,它到底是什么东西(我先假定大家的机器字长为32位): Struct WORD { unsigned int chara: 6: unsigned int font : 7; unsigned int maxsize : 19; }; Struct WORD chone; 这一段是从我编写的一个文字格式化软件摘下来的,它最多可以容纳64(既我说的unsigned int chara :6; 它总共是6位)个不同的字符值,可以处理128(既unsigned int font : 7 ;既2的7次方)种不同的字体,和2的19次方的单位长度的字。大家都可以看到maxsize是19位,它是无法被一个short int 类型的值所容纳的,我们又可以看到其余的成员的长度比char还小,这就让我们想起让他们共享32位机器字长,这就避免用一个32位的整数来表示maxsize的位段。怎么样?还要注意的是刚才的那一段代码在16位字长的机器上是无法实现的,为什么?提醒你一下,看看上面提醒的第3点,你会明白的! 你是不是发现这个东西没有用啊?如果你点头了,那你就错了!这么伟大的创造怎么会没有用呢(你对系统编程不感兴趣,相信你会改变这么一个观点的)?磁盘控制器大家应该知道吧?软驱与它的通信我们来看看是怎么实现的下面是一个磁盘控制器的寄存器: │←5→│←5→│←9→│←8→│←1→│←1→∣←1→∣←1→∣←1→∣ 上面位段从左到右依次代表的含义为:5位的命令,5位的扇区,9位的磁道,8位的错误代码,1位的HEAD LOADED,1位的写保护,1位的DISK SPINNING,1位的错误判断符,还有1位的READY位。它要怎么来实现呢?你先自己写写看: struct DISK_FORMAT { unsigned int command : 5; unsigned sector : 5; unsigned track : 9 ; unsigned err_code : 8; unsigned ishead_loaded : 1; unsigned iswrit_protect : 1; unsigned isdisk_spinning : 1; unsigned iserr_ocur : 1; undigned isready :1 ; }; 注:代码中除了第一行使用了unsigned int 来声明位段后就省去了int ,这是可行的,详见ANCI C标准。 如果我们要对044c18bfH的地址进行访问的话,那就这样: #define DISK ((struct DISK_FORMAT *)0x044c18bf) DISK->sector=fst_sector; DISK->track=fst_track; DISK->command=WRITE; 当然那些都是要宏定义的哦! 我们用位段来实现这一目的是很方便的,其实这也可以用移位或屏蔽来实现,你尝试过就知道哪个更方便了! 我们今天的话题就到这儿,如果诸位还有疑问,可e-mail给我:arhuwen@163.com; 特别声明哦:不要把以上内容用于不法行为,否则后果自负。另外本文不可用于任何谋取商业利益的举动,否则同上! -------Jeafos W.Yeh |