前陣子在弄緩存的時候,我們需要將qemu對於磁盤鏡像文件寫請求串成一個鏈表,最終將這個鏈表里面的寫請求全部刷回到鏡像文件里面,那么我們便需要一個強健,可靠的鏈表的接口,於是我們仿照Linux 2.4.0的內核,來造了這么一個鏈表的輪子。今天抽抽空來記錄一下。
鏈表,估計學過數據結構這門課程的人都對其印象深刻,因為老師們都喜歡將它放在比較靠前的地方講,很多人都認為鏈表是一種非常easy的數據結構。因為鏈表的邏輯非常地簡單,每個節點就是分成指針和數據,一頭一尾地通過指針將每個節點串起來,沒有樹形(二叉樹,B-樹,紅黑樹,基樹等)的數據結構那樣復雜的前驅和后繼的結構,以及復雜的結構伸展變化的操作。並且鏈表的各種操作的復雜度也都非常容易估算出來。
可是,要實現一個非常可靠,通用,強健的鏈表數據結構卻是很有難度的。
一般地,我們都會對一個循環鏈表的節點做出如下的定義:
struct node { int val; struct node *prev; struct node *next; };
代碼很簡單,構造完一個鏈表之后會有如下的效果:
這只是普通鏈表的實現,當我們需要創建另一種數據結構組成的鏈表的時候,如果還是按照上述的方法來創建一個鏈表,那么我們就不得不重新定義一個鏈表的節點
來存儲這種數據結構,如果某個應用里面有一萬種數據結構,那我們豈不要定義一萬種鏈表的數據結構,及其查詢,刪除,修改的應用接口?
我們可以嘗試着將鏈表單獨地抽離出來,對其進行解耦,這樣我們便可以將他當作一套通用的應用接口來使用了。
先來定義一下鏈表節點的訪問入口:
typedef struct cir_list_head { struct cir_list_head *prev, *next; }gc_list;
現在我們為特定的存儲數據來定義一個鏈表的節點:
typedef struct test_list{ gc_list list; int val; } test_list;
注意,我們在這里就將鏈表節點的訪問接口打包插入到了鏈表節點里面,仔細體會,我們最終所構建的效果如下:
接下來我們來看看如何通過gc_list * 指針來訪問這些節點里面的數據:
我們希望能夠通過結構體test_list里面的list(gc_list)來獲取數據val,也就是說在同一個結構體下,通過其中一個結構體成員來獲取另一個結構體成員的信息,
這個時候,我們就需要用到一些奇技淫巧:
#define OFFSETOF(type, member) (size_t)&(((type *)0)->member) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - OFFSETOF(type,member) );})
盜用一幅圖描述上面的這幾行代碼的意思,不用太多的文字的解釋~~,通過上面的這個接口我們便可以隨意地操控循環鏈表里面的各種數據啦~,這種封裝方法
比較好的地方便是你再也不用為每一種鏈表里面的數據特意去重寫一套API,注意這段代碼和編譯器平台相關(主要都在GCC平台下),並不具備100%的可移植性。
相關鏈接:
造的一個循環鏈表的輪子:https://github.com/jusonalien/VM-DB/blob/master/list.h
測試使用代碼:https://github.com/jusonalien/VM-DB/blob/master/TestList.c
關於container_of宏的詳盡的解釋: http://radek.io/2012/11/10/magical-container_of-macro/