contributed by <MetalheadKen
>
#define macro_name replacement-list new-line
#define
來定義一個常數或指令macro_name
替換為先前定義好的常數或指令push
和 pop
進 stack 裡,因此在執行時期會耗費大量時間成本typeof
會回傳參數內的資料型態typeof
可以依參數內的變數來動態的去調整其資料型態,可以讓程式變得更有彈性以及更容易維護
container_of
巨集的原理container_of
__extension__
根據 GCC manual 中提到,在編譯的參數裡添加 -pedantic
或 -ansi
的話,編譯器會對所有使用到 GNU extension 的地方提出警告,但是並不會對包含在 __extension__
的運算式產生警告
You can prevent such warnings within one expression by writing
__extension__
before the expression.__extension__
has no effect aside from this.
__typeof__
根據 GCC manual 中提到,若想要在 ISO C 程式中可以相容的話,可以把 typeof
改寫為 __typeof__
If you are writing a header file that must work when included in ISO C programs, write
__typeof__
instead oftypeof
.
__typeof__(((type *) 0)->member)
一般來說對 0 作 dereference 應該會造成 segmentation fault,但是在 GCC manual 中提到在使用 typeof
時只有在執行時期才能確定的 expression 才會去求值
The operand of typeof is evaluated for its side effects if and only if it is an expression of variably modified type or the name of such a type.
const __typeof__(((type *) 0)->member) *__pmember = (ptr);
member
的資料型態的指標 __pmember
,並指派一個變數 ptr
container_of
時傳進去的參數其 ptr
的資料型態與 member
的資料型態不相符時,在編譯期間會輸出 error: initialization from incompatible pointer type
的錯誤訊息,可藉此在 compile time 檢查出人為上的疏失offsetof
size_t offsetof(type, member)
offsetof
巨集會回傳在結構體 type
中,成員 member
的位址偏移量(type *) ((char *) __pmember - offsetof(type, member));
__pmember
轉型成 pointer to char 的資料型態,藉此讓之後的 pointer arithmetics 時的 offset 位移量可以為 n * sizeof(char)
Ref: cplusplus.com
__pmember
與 member
在結構體 type
中的位址偏移量相減即可得到該結構體最一開始的記憶體位址({...})
list.h
還定義一系列操作,為什麼呢?這些有什麼益處?list_add
和 list_add_tail
list_add
,而若有 FIFO 的需求則可以使用 list_add_tail
list_del_init
list_del
不同的是,list_del_init
在呼叫 list_del
來把節點從 list 移除後,還會呼叫 INIT_LIST_HEAD
來對已經移除的節點再作一次初始化list_del
中並未對已移除的節點其 prev
和 next
做初始化,有可能會對已移除的節點造成誤用的情況發生list_empty
head
是否沒有任何節點連接list_is_singular
head
是否只有一個節點連接list_splice
系列
list_add
來把一個個的節點連接上去,增加效率list_cut_position
list_move
系列
list_entry
系列
list.h
可以看到 doubly linked list 的定義為
而在使用時可以定義如下
並在走訪整個 doubly linked list 時可撰寫為
從上述程式碼中可以發現到,在走訪時我們只能取得到 struct listitem
中的 struct list_head
也就是其節點,但無法取得到節點中的資料 val
,但利用 list_entry
和其巨集內使用的 container_of
可以取得到 struct listitem
的最一開始的記憶體位址也跟著可以取得到節點中的資料 val
了總體來說,藉由定義了一系列操作,並將所有常用的操作用巨集或函式包裝起來,可以
不必再重新造輪子,加速開發時程
使其抽象化,讓程式可讀性增加
Functions should be short and sweet, and do just one thing. They should fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24, as we all know), and do one thing and do that well.
The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.
LIST_POISONING
這樣的設計有何意義?在 list.h
的函式 list_del
的註解中提到在節點移除後,會將該節點的 prev
和 next
指向一個非法存取的記憶體位置,若之後有任何程式想要去存取已經移除掉的節點的話,即造成 segmentation fault,可藉此用來在除錯階段得知哪裡有誤
根據 LITMUS wiki 中可以得知 0x00100100
和 0x00200200
為 Well-known addresses,目的為可以在 kernel oops 的時候發現是何種錯誤,因而挑選的一個特殊的位址
從任何一個節點皆可以走訪整個 list
若想存取該節點的前一個節點無須從頭開始走訪
在刪除某一節點時,其時間複雜度可為 而不是 singly linked list 的
Linux Device Driver 3/e 提到若 linked list 是環狀的,那麼每一個節點的操作都是一樣的,因此不用像 singly linked list 一樣要去維護 head
和 tail
Since Linux lists are circular, the head of the list is not generally differnet from any other entry.
list_for_each_safe
和 list_for_each
的差異在哪?"safe" 在執行時期的影響為何?safe
來預先儲存下一個節點list_del_init
或是使用 LIST_POISONING
來對移除的節點作初始化時,需要多一個變數來預先儲存下一個的節點,否則在移除節點後,因已經移除的節點的 prev
和 next
已經被改變了,故在執行 node = node->next
時會造成不預期的結果@
符號,這有何意義?你能否應用在後續的程式開發呢?tests/
目錄底下的 unit test 的作用為何?就軟體工程來說的精神為何?tests/
目錄的 unit test 可如何持續精進和改善呢?