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

深入解析 Linux 核心 container_of 宏:從結構體成員指標反推結構體位址

     編輯:關於C語言

前言:為何需要反向查找結構體?

在 C 語言開發中,我們習慣透過結構體變數或指標存取其成員,例如 st.a 或 pst->a。但實際專案中常遇到反向需求:已知結構體內某個成員的指標,如何取得外層結構體的起始位址?這在 Linux 核心、嵌入式系統、資料結構實作(如多重鏈結串列節點)中極為常見。

實戰場景:多重鏈結串列的管理難題

假設有多個區塊 B1~B5 需同時掛載於三條不同功能的單向鏈結串列:

  • 鏈結串列 1:B1 → B4 → B5
  • 鏈結串列 2:B2 → B5
  • 鏈結串列 3:B1 → B2 → B3 → B4 → B5

若使用 GLib 的 GSList,每個節點僅有一個 next 指標,無法同時表達多條鏈結關係。若在結構體內嵌入多個 GSList 成員(list1、list2、list3),遍歷鏈結串列時只能取得 GSList*,卻無法直接存取區塊資料 data。此時必須透過成員指標反推父結構體位址。

核心原理:記憶體佈局與位移運算

結構體在記憶體中按宣告順序連續排列,成員相對於結構體起始位址的位移(offset)在編譯期即確定。若已知成員位址 ptr 及其位移 offset,父結構體位址即為 (char*)ptr - offset。關鍵在於如何在程式碼中獲取編譯期位移。

Linux 核心 container_of 宏完整解析

Linux 核心提供經典實作(kernel.h):

#ifndef offsetof
#define offsetof(type, field) ((unsigned long)&((type *)0)->field)
#endif

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

關鍵技術點逐層拆解

  1. offsetof 宏:利用空指標解參考 (type *)0 取得成員位址,轉為 unsigned long 即為位移量。編譯器僅計算位移,不產生實際存取指令。
  2. typeof() 擴展:GCC 擴展語法,獲取運算式型別。此處用於宣告 __mptr 型別與成員一致,確保型別安全。
  3. Statement Expression ({...}):GCC 擴展,允許在巨集中使用區塊陳述式並回傳值。範例:({int i=1; i+2;}) 值為 3。
  4. 指標運算:(char *)__mptr 以位元組為單位減去位移,再轉回目標結構體指標型別。

實戰範例:透過 double* 存取同結構體的 long long

#include <stdio.h>

#ifndef offsetof
#define offsetof(type, field) ((unsigned long)&((type *)0)->field)
#endif

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

typedef struct _A {
    int a;
    char b;
    long long c;
    double d;
} SA;

SA sa = { .a = 10, .b = 'c', .c = 204, .d = 3.14 };

int main(void) {
    double *pd = &sa.d;
    // 已知 pd 指向 sa.d,透過 container_of 取得 sa 位址並存取 c
    printf("SA.c = %lld\n", container_of(pd, SA, d)->c);
    return 0;
}

輸出: SA.c = 204

應用回顧:解決多重鏈結串列問題

定義區塊結構體:

struct _block {
    int data;
    GSList list1;
    GSList list2;
    GSList list3;
};

遍歷 list1 時,節點指標實為 &block->list1。呼叫 container_of(node, struct _block, list1) 即可取得 block 位址,進而存取 data 與其他鏈結節點。

總結

container_of 以極簡巨集實現編譯期位移計算與執行期指標運算,零額外開銷、型別安全、廣泛應用於核心資料結構。掌握其原理不僅能解決反向查找難題,更能深入理解 C 語言記憶體模型與編譯器機制。

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