首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言進階技巧:根據成員地址反推結構體首地址——offsetof 與 container_of 詳解

     編輯:關於C語言

問題引入

給定一個結構體實例的某成員變量地址,如何計算出該結構體實例的起始地址?這是 Linux 內核、嵌入式驅動、容器化框架中極其常見的需求。

struct A {
    int x;
    int y;
    int z;
};

#define GET_HEADER_ADDR(MEMBER)  // 你來實現

void main() {
    struct A myTest;
    printf("addr is %p", GET_HEADER_ADDR(myTest.z));
}

核心宏:offsetof

定義

#define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE *)0)->MEMBER))

原理解析

  1. (TYPE *)0:將地址 0 強轉為類型指針,欺騙編譯器以此為基址
  2. ((TYPE *)0)->MEMBER:訪問成員,編譯器計算其相對基址 0 的偏移量
  3. &(...):取該成員地址,數值恰好等於偏移量
  4. (size_t):轉為無符號整數類型

示例

struct A { int x; int y; int z; };
// 假設 int 4 字節,無填充
offsetof(struct A, x) → 0
offsetof(struct A, y) → 4
offsetof(struct A, z) → 8

注意:此宏依賴編譯器對零地址解引用的行為,C 標準將其定義為未定義行為,但在所有主流編譯器(GCC、Clang、MSVC)上均能正常工作。C++11 起標準庫提供 std::offsetof。

核心宏:container_of

定義

#define container_of(ptr, type, member) ({ \
    const typeof(((type *)0)->member) *__mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); \
})

逐層拆解

  1. typeof(((type *)0)->member):GCC 擴展,獲取成員類型(如 int)
  2. const typeof(...) *__mptr = (ptr):定義指針 __mptr 指向傳入的成員地址,const 防止誤修改
  3. (char *)__mptr:轉為字節指針,便於字節級算術
  4. offsetof(type, member):獲取成員在結構體中的偏移
  5. (char *)__mptr - offsetof(...):成員地址減去偏移 = 結構體起始地址
  6. (type *):轉回結構體指針類型
  7. ({ ... }):GCC 語句表達式擴展,返回最後一條表達式的值

使用示例

struct A { int x; int y; int z; };
struct A myTest;
int *pz = &myTest.z;

struct A* header = container_of(pz, struct A, z);
// header == &myTest

實戰應用場景

1. 內核鏈表

struct list_head { struct list_head *next, *prev; };
struct task_struct {
    // ... 其他字段 ...
    struct list_head tasks;  // 掛載在全局任務鏈表中
};

// 遍歷任務鏈表
struct list_head *pos;
list_for_each(pos, &init_task.tasks) {
    struct task_struct *task = container_of(pos, struct task_struct, tasks);
    // 使用 task 指針訪問完整進程信息
}

2. 異構對象池

struct PoolHeader { size_t size; struct PoolHeader *next; };
void* allocate(size_t size) {
    struct PoolHeader *hdr = malloc(sizeof(struct PoolHeader) + size);
    hdr->size = size;
    return (char*)hdr + sizeof(struct PoolHeader);  // 返回用戶數據區
}
void deallocate(void* ptr) {
    struct PoolHeader *hdr = container_of(ptr, struct PoolHeader, /* 無需成員名,直接偏移 */);
    // 實際需定義柔性數組成員或用字節運算
    free(hdr);
}

3. C++ 多重繼承中的 this 指針調整

編譯器實現 dynamic_cast、虛基類指釖調整時,本質上也是類似的偏移計算。

移植到標準 C/C++

GCC/Clang 擴展(typeof、語句表達式)在 MSVC 不支援。標準化替代方案:

// C11 _Generic + 宏(較繁瑣)
// C++17 模板函數(推薦)
template<typename T, typename M>
T* container_of(M* ptr, M T::*member_ptr) {
    return reinterpret_cast<T*>(
        reinterpret_cast<char*>(ptr) - offsetof(T, member_ptr)
    );
}

// 使用
struct A { int x, y, z; };
A a;
int* pz = &a.z;
A* pa = container_of(pz, &A::z);  // 類型安全,無宏副作用

常見陷阱與避坑

陷阱後果對策
傳入非成員指針計算出錯誤地址,導致訪問越界僅對確知為結構體成員的指針使用
結構體有填充字節offsetof 自動包含填充,計算正確無需特殊處理,offsetof 已處理對齊
成員為位域offsetof 不支援位域避免在需 container_of 的結構體中使用位域
多重繼承/虛繼承 (C++)this 指針調整複雜,單純偏移失效使用 dynamic_cast 或標準庫工具

總結

offsetof 與 container_of 是 C 語言「以結構體成員為入口訪問整體對象」的核心技術,體現了「數據結構即接口」的設計哲學。掌握其原理、擴展機制與移植方案,是從 C 語言使用者進階為系統級開發者的關鍵一步。現代 C++ 建議優先使用類型安全的模板封裝,減少宏帶來的維護風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved