讓我給你舉個例子:
struct A
{
int some_data;
int other_data;
};
現在讓我們假設有這樣的功能:
int get_some_data_from_other(int *other)
{
struct A *a = container_of(other, struct A, other_data);
return a->some_data;
}
正如你所看到的,我們能判斷出什麼是原始struct A
包含給定int *other
,通過知道指針所指向的結構的哪個字段。 這裏的關鍵是我們不要有一個引用結構本身,但只是一個指向其成員之一。
這看起來很荒謬,但在一些非常聰明的結構中實際上是有用的。內核創建鏈表的方式就是一個很常見的例子。我建議你閱讀this post on kernelnewbies.org。讓我們來看看它的一個簡單的例子:
struct whatever
{
/* whatever data */
struct list_head mylist;
};
所以struct whatever
有一些數據,但同時也希望作爲一個鏈表中的一個節點。他們在學校教你的是有一個不同的結構,其中包含next
/prev
指針以及指向struct whatever
(或void *
)的指針。這樣,您就擁有了通過其獲取數據的節點。
按照所有軟件工程標準,這實際上是一件好事。但是軟件工程標準對於效率並沒有多少關注。見Why should I have written ZeroMQ in C, not C++ (part II)。底線是,與傳統方法一樣,您必須與數據節點分開分配鏈接列表節點,即將內存分配,取消分配,碎片化和緩存未命中等開銷加倍。 Linux內核的做法恰恰相反。每個數據節點都包含一個通用鏈接列表節點。通用鏈接列表節點不知道有關數據的任何內容或分配方式,只知道如何連接到其他鏈接列表節點。
所以,讓我們深入瞭解一下:
+-------------------+ +---------------------+ +---------------------+
| | | | | |
| WHATEVER DATA | | WHATEVER DATA 2 | | WHATEVER DATA 3 |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
+-------------------+ +---------------------+ +---------------------+
| |----->| |----->| |
| mylist | | mylist 2 | | mylist 3 |
| |<-----| |<-----| |
+-------------------+ +---------------------+ +---------------------+
你有什麼作爲鏈表都在裏面struct list_head
指針指向其他struct list_head
秒。請注意,他們不指向struct whatever
,而是指向mylist
裏面的這些結構。
假設你有一個節點,struct whatever w
。你想找到下一個節點。你能做什麼?首先,您可以執行w.mylist.next
以獲取指向下一個節點的mylist
的指針。現在您必須能夠提取包含該節點的實際struct whatever
。這就是container_of
使用:
struct whatever w_next = container_of(w.mylist.next, struct whatever, mylist);
最後,要注意的是,Linux內核宏遍歷了一個鏈表的所有節點,這往往不是你想要的東西,所以你實際上並不需要直接使用container_of
。
原因在於維護。如果每個人都使用這個宏,那麼只需編輯這個宏,就可以輕鬆地改變指針分配的方式。 –