編寫 C 語言程式碼時,指標無處不在。我們可以稍微額外利用指標,在它們內部暗中儲存一些額外資訊。為實現這一技巧,我們利用了資料在記憶體中的自然對齊特性。
記憶體中的資料並非儲存在任意地址。處理器通常按照其字大小相同的塊讀取記憶體資料;那麼考慮到效率因素,編譯器會按照塊大小的整數倍對記憶體中的實體進行地址對齊。因此在 32 位的處理器上,一個 4 位元組整型資料肯定存放在記憶體地址能被4整除的地方。
下面,假設系統中整型資料和指標大小均為 4 位元組。
現在有一個指向整型的指標。如上所述,整型資料可以存放在記憶體地址 0x1000 或者 0x1004 或者 0x1008,但是決不會存放在 0x1001 或者0x1002 或者 0x1003 或者其他不能被4整除的任何地址。所有是4整數倍的二進位制數都是以 00 結尾。實際上,這意味著對於所有指向整型的指標,它的最後兩位總是 0。
那麼有 2 位元沒有承載任何資訊。此處的技巧是將我們的資料放置到這兩個位元中,在需要時使用,並在通過指標解引用來訪問記憶體前刪除它們。
由於 C 標準對指標位操作的支援不是很好,所以我們將指標儲存為一個無符號整型資料。
下面是一段簡短的簡單程式碼片段。完整的程式碼檢視 github 程式碼倉庫中的 hide-data-in-ptr。
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 33 34 35 |
void put_data(int *p, unsigned int data) { assert(data < 4); *p |= data; } unsigned int get_data(unsigned int p) { return (p & 3); } void cleanse_pointer(int *p) { *p &= ~3; } int main(void) { unsigned int x = 701; unsigned int p = (unsigned int) &x; printf("Original ptr: %un", p); put_data(&p, 3); printf("ptr with data: %un", p); printf("data stored in ptr: %un", get_data(p)); cleanse_pointer(&p); printf("Cleansed ptr: %un", p); printf("Dereferencing cleansed ptr: %un", *(int*)p); return 0; } |
程式碼輸出如下:
1 2 3 4 5 |
Original ptr: 3216722220 ptr with data: 3216722223 data stored in ptr: 3 Cleansed ptr: 3216722220 Dereferencing cleansed ptr: 701 |
我們可以在指標中儲存任何可以用兩個位元位表示的資料。使用 put_data() 函式,設定指標的最低兩位為要儲存的資料。該資料可以使用get_data() 函式獲取。此處除了最後兩位所有的位都被覆蓋為零,於是我們隱藏的資料就顯示出來。
cleanse_pointer() 函式將最低兩位置零,保證指標安全地解引用。注意雖然有些 CPU(像 Intel 允許我們訪問未對齊記憶體地址,但其餘 CPU(像 ARM)會出現訪問錯誤。所以,要牢記在解引用前保證指標指向已對齊記憶體地址。
這在實際中有應用嗎?
是的,有應用。檢視 Linux 核心中紅黑樹的實現(連結)。
樹的結點定義如下:
1 2 3 4 5 |
struct rb_node { unsigned long __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); |
此處 unsigned long __rb_parent_color 儲存瞭如下資訊:
- 父節點的地址
- 結點的顏色
色彩的表示用 0 代表紅色,1 代表黑色。
和前面的例子一樣,該資料隱藏在父指標“無用的”位元位中。
下面看一下父指標和色彩資訊是如何獲取的:
1 2 |
/* in rbtree.h */ #define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3)) |
1 2 3 |
/* in rbtree_augmented.h */ #define __rb_color(pc) ((pc) & 1) #define rb_color(rb) __rb_color((rb)->__rb_parent_color) |
記憶體中每一位元都很珍貴,我們們永遠不要浪費。——(本文作者)