# 【Linux】Linux核心原始程式碼巨集 container_of 以及 offsetof ## 簡介 這兩個巨集對於Linux核心原始程式碼非常的重要,它適用的範圍非常廣泛,被應用在**Linked List**與**hash Table**這一通用的資料結構中,用來化簡程式設計,並讓C語言也具備物件導向的行為,也是Linux物件導向中相當重要的機制! ## [offsetof 巨集](https://github.com/torvalds/linux/blob/master/tools/include/linux/kernel.h) ```c #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif ``` - **目的** : 取得結構體內成員相對於結構體起始位置的位移量 - `size_t` : 表示一個byte的單位 :::success **操作細節** * `(TYPE *)0` : 將 `0` (數值意義上)轉型成 `type` 型別的指標 * `&((TYPE *)0)->MEMBER` : 取得其成員記憶體位址 ::: > 編譯器處理 `&((type *)0)->member` 時,不會真正去存取地址為 0 的記憶體區段,只是將 0(數值意義上) 看做指標操作的一個運算元,除非額外有**dereference**操作。 https://hackmd.io/@sysprog/linux-macro-containerof ## [container_of 巨集](https://github.com/torvalds/linux/blob/master/include/linux/container_of.h) ```c #ifndef container_of /** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * */ #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); }) #endif ``` - **目的** : 給定結構體內某成員的指標,求得原結構體的記憶體位置 - **應用** : - **模擬繼承與多態** : 常用於模擬 C 語言中的「繼承」,使子結構體可以引用到父結構體的屬性或方法。 - **通用資料結構設計** : **雙向列表(doubly Linked List)** 和 **哈希表(Hash-Table)** 在Linux核心原始程式碼中採用這樣設計模式,使其能夠嵌入至任意資料結構中並進行操作 **操作細節** * 使用`__typeof__` 取得 `type` 中 `member` 的資料型態,並且將 `ptr` 指派給 `__pmember` * `__pmember` 代表該 `type` 資料結構中成員的資料型態 * 接著透過 `offsetof(type, member)` 取得 `member` 在 `type` 中的offset * 將絕對地址 `(char *) __pmember` 減去 `offsetof(type, member)` 即可得到結構體的起始位 > 轉型成 `char *` 目的是確保地址操作是以 **1 byte** 為基準,由於 `char *` 不論在哪裡都是1 byte(C語言標準),藉由這樣轉換方式能夠確保指標操作不會有問題 下面這張清楚地說明 `container_of` 的操作 ![image](https://hackmd.io/_uploads/S145SsACp.png =75%x) 可以透過`contain_of`設計以下的物件導向的概念 - **封裝(encapsulation)** - **繼承(inheritance)** - **多型(polymorphism)** :::success 在C語言上可以透過函數指標將方法綁定在特定資料結構上,所以C語言是需要透過一些語法或巨集來達成封裝的效果,雖然有語法的限制,但並不影響物件導向的精神 ::: > 物件導向是一種態度,語法只是輔助 > Jserv ## Example ```cpp #include <linux/kernel.h> #include <stddef.h> #include <stdio.h> #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) // struct data // { // short a; // char b; // double c; // }__attribute__((packed)); // align memory struct data { short a; char b; double c; }; int main(int argc, char **argv) { struct data x = { .a = 25, .b = 'A', .c = 12.45 }; char *p = (char *) &x; printf("a=%d\n", *((short *) p)); p += sizeof(short); printf("b=%c\n", *((char *) p)); p += sizeof(char); printf("c=%lf\n", *((double *) p)); printf("\n*****use offsetof *****\n"); p = (char *) &x; printf("a=%d\n", *((short *) p)); p += offsetof(struct data, b); printf("b=%c\n", *((char *) p)); p = (char *) &x; p += offsetof(struct data, c); printf("c=%lf\n", *((double *) p)); return 0; } ``` ## 參考資料 1. [Linux 核心原始程式碼巨集: container_of](/odsx15lMRDiqsuQDL8LE8g)