給定結構體某個成員的地址,如何計算該結構體實例的起始地址?這是 Linux 內核中著名的 container_of 宏的核心應用場景,廣泛用於鏈表、設備驅動、對象模型等子系統。
#define STRUCT_OFFSET(struct_name, member) \
((unsigned long)&((struct struct_name *)0)->member)
假設結構體定義:
struct stru_addr {
int a; // 偏移 0
char b; // 偏移 4
int d; // 偏移 8 (對齊)
char c; // 偏移 12
};
虛擬地址 0 強轉為結構體指針:
(struct stru_addr *)0
│
├─> a @ 偏移 0
├─> b @ 偏移 4
├─> d @ 偏移 8
└─> c @ 偏移 12 ← &((struct stru_addr*)0)->c == 12
因為基址為 0,成員地址 等於 其相對偏移量。此技巧利用編譯器常量折疊,零開銷獲取編譯期常量偏移。
#include <stdio.h>
#include <stddef.h> // offsetof 宏標準定義
#define STRUCT_OFFSET(struct_name, member) \
((unsigned long)&((struct struct_name *)0)->member)
struct stru_addr {
int a;
char b;
int d;
char c;
};
int main(void) {
struct stru_addr s;
printf("s 起始地址: %p\n", (void *)&s);
printf("s.a 地址: %p\n", (void *)&s.a);
printf("s.c 地址: %p\n", (void *)&s.c);
// 方法一:自定義宏
unsigned long offset = STRUCT_OFFSET(stru_addr, c);
printf("c 偏移量(自定義): %lu\n", offset);
printf("反推起始地址: %p\n", (void *)((char *)&s.c - offset));
// 方法二:標準庫 offsetof (C89 起標準化,推薦使用)
printf("c 偏移量(offsetof): %zu\n", offsetof(struct stru_addr, c));
printf("反推起始地址: %p\n", (void *)((char *)&s.c - offsetof(struct stru_addr, c)));
return 0;
}
s 起始地址: 0x7ffd12345670 s.a 地址: 0x7ffd12345670 s.c 地址: 0x7ffd1234567c c 偏移量(自定義): 12 反推起始地址: 0x7ffd12345670 c 偏移量(offsetof): 12 反推起始地址: 0x7ffd12345670
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
struct list_head {
struct list_head *next, *prev;
};
struct my_struct {
int data;
struct list_head list; // 嵌入鏈表節點
};
// 已知 list 節點地址,獲取包含結構體指針
struct list_head *pos = ...;
struct my_struct *p = container_of(pos, struct my_struct, list);
| 技術點 | 說明 |
|---|---|
| 零地址強轉 | 利用編譯器計算成員偏移,無運行時開銷 |
| 對齊自動處理 | 編譯器按 ABI 規則插入填充字節,偏移量已含對齊 |
| offsetof 宏 | 標準 C 庫提供,定義於 <stddef.h>,優先使用 |
| 指針算術 | 用 char* 按字節偏移,避免類型大小影響 |
| typeof (GNU) | 內核宏用 typeof 保持類型安全,標準 C 用 void* 轉換 |
list_head 嵌入業務結構體,遍歷時反推宿主對象cdev、platform_device 等嵌入模型char* 字節訪問是合法的offsetof,可讀性更好且標準化這個技巧展示了 C 語言「指針即地址、結構體即內存佈局」的底層本質,是系統級編程的核心內功之一。