• 文章
  • 厌倦了移位操作?
发布
2008年1月14日

厌倦了移位操作?

评分:3.8/5 (35 票)
*****
我看了太多使用位移和按位与来从更大的数据类型中提取值的源代码 (例如,从 32 位整数中提取 IP 地址)

这很容易出错。我将向您展示一种更简单的方法,使用 C 中的“联合”(union)。

C 联合只是引用同一内存地址的简单方法,但使用多个符号标签。

好吧,它没那么“简单”,所以这里有一个稍微简单的解释。您知道在给变量赋名字时,您实际上是在给一块内存一个符号,一个您可以用来引用它的 (符号) 名称。所以当我们写 `int x` 时,我们实际上是在给一块 4 字节 (在 32 位机器上) 的内存位置一个标签“x”,我们可以在代码中引用它。

联合更进一步,它允许我们使用两个或多个名称来引用同一内存位置,并将它们作为两个或多个数据类型来访问。

我将创建一个结构,它允许我将一个 32 位整数放入一个位置,然后从同一位置提取单个字节值,或者相反,写入 4 个单独的字节,并获得 32 位整数值,所有这些操作都不需要任何移位或逻辑与操作。

我分两步完成:
  1. 声明一个由 4 个字节组成的结构(确切地说,是 4 个 `unsigned char`),我可以将它们地址化为 `b0`、`b1`、`b2` 和 `b3`。
  2. 声明一个联合,它允许这个 4 字节结构与一个无符号 4 字节整数占用相同的内存地址。
这是代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>

typedef struct packed_int {
		 unsigned char b0;
		 unsigned char b1;
		 unsigned char b2;
		 unsigned char b3;
} packed_int;

typedef union {
	unsigned int i;
	packed_int b;
} packed;


好的,现在我只需要一些代码来“测试”这些结构,向您展示它们确实能如我所说的那样工作,而源代码中没有任何花哨的位移操作……(请注意,我没有包含处理“字节序”(endian-ness)的代码——这会通过使用编译时常量来有条件地包含,但为了清晰起见,这里省略了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

int main(int argc, char* argv[], char* env[]) {
	packed v;    /* this is my "dual-purpose" 32-bit memory location */

	v.i =  3232235881UL; /* I assign it an internet address in 32-bit format */

	/* next, I'll print out the individual bytes */
	printf("the bytes are %d, %d, %d, %d\n", v.b.b0, v.b.b1, v.b.b2, v.b.b3);

	/* and just to prove that the 32-bit integer is still there ... print it out too */
	printf("the value is %u\n", v.i);

	/* just for the heck of it, increment the 32-bit integer */
	v.i++;
	printf("after v.i++, the bytes are %d, %d, %d, %d\n", v.b.b0, v.b.b1, v.b.b2, v.b.b3);

	/* now do the reverse, assign 70.80.90.100 as an ip address */
	v.b.b0 = 70;
	v.b.b1 = 80;
	v.b.b2 = 90;
	v.b.b3 = 100;

	/* .. and extract the 32-bit integer value */
	printf("the value is %u\n", v.i);

	/* show that 70.80.90.100 is really what we put in there */
	printf("the bytes are %d, %d, %d, %d\n", v.b.b0, v.b.b1, v.b.b2, v.b.b3);

	/* ok, we're done here */
	return EXIT_SUCCESS;
}


这比创建一些怪异的宏来进行位操作要容易得多,不是吗?顺便说一句,同样的技巧也适用于 `point_t`、`rect_t`、`time_t`、64 位、128 位、256 位值,以及单个位。

在未来的帖子中,我将向您展示如何编写代码来选择单个位,而无需理会位掩码。

我的原始文章来源: http://trolltalk.com/index.php?name=News&file=article&sid=2