---
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) );})
```