• 文章
  • 厭倦了移位操作?
釋出
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