前陣子在弄緩存的時候,咱們須要將qemu對於磁盤鏡像文件寫請求串成一個鏈表,最終將這個鏈表裏面的寫請求所有刷回到鏡像文件裏面,那麼咱們便須要一個強健,可靠的鏈表的接口,因而咱們仿照Linux 2.4.0的內核,來造了這麼一個鏈表的輪子。今天抽抽空來記錄一下。node
鏈表,估計學過數據結構這門課程的人都對其印象深入,由於老師們都喜歡將它放在比較靠前的地方講,不少人都認爲鏈表是一種很是easy的數據結構。由於鏈表的邏輯很是地簡單,每一個節點就是分紅指針和數據,一頭一尾地經過指針將每一個節點串起來,沒有樹形(二叉樹,B-樹,紅黑樹,基樹等)的數據結構那樣複雜的前驅和後繼的結構,以及複雜的結構伸展變化的操做。而且鏈表的各類操做的複雜度也都很是容易估算出來。git
但是,要實現一個很是可靠,通用,強健的鏈表數據結構倒是頗有難度的。github
通常地,咱們都會對一個循環鏈表的節點作出以下的定義:緩存
struct node { int val; struct node *prev; struct node *next; };
代碼很簡單,構造完一個鏈表以後會有以下的效果:數據結構
這只是普通鏈表的實現,當咱們須要建立另外一種數據結構組成的鏈表的時候,若是仍是按照上述的方法來建立一個鏈表,那麼咱們就不得不從新定義一個鏈表的節點測試
來存儲這種數據結構,若是某個應用裏面有一萬種數據結構,那咱們豈不要定義一萬種鏈表的數據結構,及其查詢,刪除,修改的應用接口?spa
咱們能夠嘗試着將鏈表單獨地抽離出來,對其進行解耦,這樣咱們即可以將他看成一套通用的應用接口來使用了。3d
先來定義一下鏈表節點的訪問入口:指針
typedef struct cir_list_head { struct cir_list_head *prev, *next; }gc_list;
如今咱們爲特定的存儲數據來定義一個鏈表的節點:code
typedef struct test_list{ gc_list list; int val; } test_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/