您的位置:首页 > 编程语言 > C语言/C++

C语言基础--位段类型

2009-10-09 16:47 176 查看

1.什么是位段

------------------------------------------------------------

在前面各章中,
我们已经讨论过字节概念了。在大多数的计算机系统中,
一个字节是由八个更小的,
称作为位的单位组成的。位是比字节更小的单位。位只有两个值, 1
或 0
。因此,
存储在计算机存储器中的一个字节可以看成由八个二进制数字形成的串。

例如, 一个存放值 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。该结构定义了五个成员。第一个成员叫做
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]。

最后, 不可以取位段地址。原因是, 在这种情况不, 显然没有称作为
"位段指针" 类型的变量。

这里, 我们再深入讨论一下位段。如果使用下面的结构定义:



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;

};

将定义一个结构 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
------------------------------------------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: