C 語言無「類」概念,但內核大量使用「嵌入式結構體」模擬繼承:
struct file {
struct list_head f_list; // 嵌入鏈表節點
// ... 其他成員
};
給定 struct list_head *ptr,如何反推包含它的 struct file *?
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
(TYPE *)0:將地址 0 強轉為類型指針,構造一個「虛擬對象」位於地址 0。&((TYPE *)0)->MEMBER:取該虛擬對象中 MEMBER 的地址,即成員相對結構體首地址的字節偏移。size_t,編譯期常量,無運行開銷。typeof(expr) 返回 expr 的類型。typeof(((type *)0)->member) 獲得 member 的聲明類型(如 struct list_head)。// 假設調用:container_of(ptr, struct file, f_list)
const typeof(((struct file *)0)->f_list) *__mptr = ptr;
// 等價於:struct list_head *__mptr = ptr; // 類型安全賦值
// 計算容器地址:
(char *)__mptr - offsetof(struct file, f_list)
// 成員地址 - 成員偏移 = 結構體首地址
// 強轉回目標類型指針
(struct file *)((char *)__mptr - offsetof(struct file, f_list))
struct file 實例地址: 0xA000 +------------------+ 0xA000 | 其他成員... | +------------------+ 0xA010 ← f_list 偏移 0x10 | struct list_head | ← ptr 指向這裡 (0xA010) | next, prev | +------------------+ 0xA018
offsetof = 0x10,ptr = 0xA010,0xA010 - 0x10 = 0xA000 即容器地址。
#include
#include // offsetof
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
struct student {
char name[20];
char sex;
int age;
} stu = {"ZhangSan", 'M', 20};
int main() {
// 通過 &stu.sex 反推 stu 地址
struct student *p = container_of(&stu.sex, struct student, sex);
printf("name: %s, sex: %c, age: %d\n", p->name, p->sex, p->age);
// 驗證 offsetof
printf("offsetof(sex) = %zu\n", offsetof(struct student, sex)); // 輸出 20
return 0;
}
list_for_each(pos, head) { my_struct *p = container_of(pos, my_struct, list); ... }typeof 確保 ptr 與 member 類型匹配,編譯期報錯。typeof、語句表達式 ({...})),移植需適配(如 MSVC 用 CONTAINING_RECORD)。#define CONTAINING_RECORD(address, type, field) \
((type *)((char *)(address) - offsetof(type, field)))
無 typeof 檢查,使用者需自行保證類型正確。