如何在指標中隱藏資料?

湯曉發表於2014-08-06

編寫 C 語言程式碼時,指標無處不在。我們可以稍微額外利用指標,在它們內部暗中儲存一些額外資訊。為實現這一技巧,我們利用了資料在記憶體中的自然對齊特性。

記憶體中的資料並非儲存在任意地址。處理器通常按照其字大小相同的塊讀取記憶體資料;那麼考慮到效率因素,編譯器會按照塊大小的整數倍對記憶體中的實體進行地址對齊。因此在 32 位的處理器上,一個 4 位元組整型資料肯定存放在記憶體地址能被4整除的地方。

下面,假設系統中整型資料和指標大小均為 4 位元組。

現在有一個指向整型的指標。如上所述,整型資料可以存放在記憶體地址 0x1000 或者 0x1004 或者 0x1008,但是決不會存放在 0x1001 或者0x1002 或者 0x1003 或者其他不能被4整除的任何地址。所有是4整數倍的二進位制數都是以 00 結尾。實際上,這意味著對於所有指向整型的指標,它的最後兩位總是 0。

那麼有 2 位元沒有承載任何資訊。此處的技巧是將我們的資料放置到這兩個位元中,在需要時使用,並在通過指標解引用來訪問記憶體前刪除它們。

由於 C 標準對指標位操作的支援不是很好,所以我們將指標儲存為一個無符號整型資料。

下面是一段簡短的簡單程式碼片段。完整的程式碼檢視 github 程式碼倉庫中的 hide-data-in-ptr

程式碼輸出如下:

我們可以在指標中儲存任何可以用兩個位元位表示的資料。使用 put_data() 函式,設定指標的最低兩位為要儲存的資料。該資料可以使用get_data() 函式獲取。此處除了最後兩位所有的位都被覆蓋為零,於是我們隱藏的資料就顯示出來。

cleanse_pointer() 函式將最低兩位置零,保證指標安全地解引用。注意雖然有些 CPU(像 Intel 允許我們訪問未對齊記憶體地址,但其餘 CPU(像 ARM)會出現訪問錯誤。所以,要牢記在解引用前保證指標指向已對齊記憶體地址。

這在實際中有應用嗎?

是的,有應用。檢視 Linux 核心中紅黑樹的實現(連結)。

樹的結點定義如下:

此處 unsigned long __rb_parent_color 儲存瞭如下資訊:

  1. 父節點的地址
  2. 結點的顏色

色彩的表示用 0 代表紅色,1 代表黑色。

和前面的例子一樣,該資料隱藏在父指標“無用的”位元位中。

下面看一下父指標和色彩資訊是如何獲取的:


記憶體中每一位元都很珍貴,我們們永遠不要浪費。——(本文作者)

相關文章