--- tags: data structure, linux kernel, container_of --- # Linux Kernel 中的 container_of 巨集 `container_of` 巨集被定義在 [<linux/kernel.h>](https://elixir.bootlin.com/linux/v4.0/source/include/linux/kernel.h#L799) 中。 `container_of` 需要使用到 `offsetof` 巨集幫忙計算結構 (struct) 起始位址到結構成員 (member) 間的偏移量 (offset)。只要知道結構中任一成員的偏移量就可以計算出以該結構為型別 (type) 之物件在記憶體中的起始位址。 關於 `offsetof` 的相關說明可以參考 [Linux Kernel 中的 offset 巨集](https://hackmd.io/@hsuedw/offsetof) 。 ```c #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) ``` ## typeof 的功用 首先,瞭解一下 `typeof` 的作用。 `typeof` 是 `gcc` 的 extension 。 ==`typeof` 主要功能是讓編譯器在編譯時期推算運算式 (expression) 之結果的型別。== > [6.7 Referring to a Type with typeof](https://gcc.gnu.org/onlinedocs/gcc/Typeof.html) 寫個小程式來驗證一下。 ```c= #include <stdio.h> #include <limits.h> int main(int argc, char *argv[]) { int a = INT_MAX; long b = 1; printf("sizeof(int): %ld, sizeof(long): %ld, sizeof(a + b): %ld\n", sizeof(int), sizeof(long), sizeof(typeof(a + b))); printf("a: %d, b: %ld, a + b: %ld\n", a, b, a + b); int c = 1; printf("sizeof(a + c): %ld\n", sizeof(typeof(a + c))); printf("a: %d, c: %d, a + c: %d\n", a, c, a + c); return 0; } ``` 將上述程式編譯並執行後,可得下列結果。 ``` $ gcc test.c -o test $ ./test sizeof(int): 4, sizeof(long): 8, sizeof(a + b): 8 a: 2147483647, b: 1, a + b: 2147483648 sizeof(a + c): 4 a: 2147483647, c: 1, a + c: -2147483648 ``` 由 C99 spec 可知,一個 `int` 物件與一個 `long` 物件相加所得結果之型別為 `long` 。 > C99 spec, 6.3.1.8 Usual arithmetic conversions > Otherwise, if both operands have signed integer types or both have unsigned > integer types, the operand with the type of lesser integer conversion rank is > converted to the type of the operand with greater rank. 第 6 行將 `int` 變數 `a` 初始化為 `INT_MAX` (即 `2147483647` ) 。 第 7 行將 `long` 變數 `b` 初始化為 `1` 。 所以第 9 行的運算式 `a + b` 的結果並沒有產生溢位 (overflow) 。其結果為 `2147483648` 。且由 `a + b` 運算結果的大小為 8 bytes 可知, `typeof(a + b)` 推得 `a + b` 的型別為 `long` 。 另外,由於變數 `a` 與 `c` 的型別皆為 `int` ,所以運算式 `a + c` 的型別也是 `int` 。因此, `a + c` 會產生溢位,其結果為 `-2147483648` 。 ## `container_of` 巨集的作用 `(type *)0)->member` 可以找到結構中的 `member` 成員。因此, `typeof( ((type *)0)->member )` 就會推得 `member` 的型別。假設 `member` 的型別為 `int` ,則 `__mptr` 的型別就是 `int *` (pointer to `int`) 。 因為`__mptr` 是物件中 `member` 的記憶體位址,所以 `(char *)__mptr - offsetof(type,member)` 的運算結果就是以 `type` 為型別之物件在記憶體中的起始位址。 由 [Linux Kernel 中的 offset 巨集](https://hackmd.io/@hsuedw/offsetof) 中的討論可知, `offsetof(type,member)` 的作用是計以 `type` 為型別之算物件的起始位址到 `memeber` 間的偏移量。所以, ==`container_of` 巨集就是透過物件的某個成員在記憶體中的位址 (`ptr`) ,結構的名稱(就是物件的型別, `type`) 以及該成員的名稱 (`member`) 計算出這個以 `type` 為型別之物件在記憶體中的起始位址。== ## 範例 ```c #include <stdio.h> #include <limits.h> #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) );}) struct test { int data_a; long data_b; double data_c; }; int main(int argc, char *argv[]) { struct test obj = {123, 456L, 789.0}; struct test *p = container_of(&obj.data_c, struct test, data_c); printf("p->data_a: %d\n", p->data_a); printf("p->data_b: %ld\n", p->data_b); printf("p->data_c: %f\n", p->data_c); return 0; } ``` ``` $ gcc test.c -o test $ ./test p->data_a: 123 p->data_b: 456 p->data_c: 789.000000 ``` ## `container_of` 巨集的另一種實作 下列程式碼片段是 `container_of` 巨集的另一種實作。與原本的實作有相同的效果。 ```c #define container_of(ptr, type, member) ({ \ const typeof( ptr ) __mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) ```