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

C語言進階技巧:宏定義陷阱、通用數據結構與結構體地址計算

     編輯:關於C語言

宏定義的 do { ... } while(0) 慣用法

在 Linux 內核等大型 C 專案中,常見如下宏定義模式:

#define DUMP_WRITE(addr, nr) \
    do { \
        memcpy(bufp, addr, nr); \
        buf += nr; \
    } while(0)

為何不用大括號 { ... } 或直接多語句?

  • 直接多語句:#define MACRO stmt1; stmt2; 若用於 if (cond) MACRO; 僅 stmt1 受 if 控制,stmt2 無條件執行 → 邏輯錯誤。
  • 大括號 { ... }:#define MACRO { stmt1; stmt2; } 用於 if (cond) MACRO; else ... 時,分號導致 if 提前結束,else 無匹配 if → 語法錯誤。
  • do { ... } while(0):構成單一語句,配合結尾分號,完美適配 if-else、循環等所有語境,且編譯器會優化掉無意義循環,零開銷。

宏實現通用數據結構操作

為提高數據結構通用性,可用宏對結構體進行操作。例:雙向鏈表頭初始化:

#define INIT_LIST_HEAD(ptr) \
    do { \
        (ptr)->next = (ptr); \
        (ptr)->prev = (ptr); \
    } while(0)

同樣利用 do-while(0) 確保語句完整性。現代 C 也可用 inline 函數替代,兼具類型安全與效能。

結構體成員地址反推結構體首地址:container_of 核心原理

Linux 內核廣泛使用的 container_of 宏,能從結構體任意成員地址反推結構體起始地址:

#define get_struct_addr_from_member_addr(p, stru, m) \
    ( (stru*) ( (char*)p - (char*)&(((stru*)0)->m) ) )

核心機制拆解

  1. (stru*)0:將地址 0 強轉為 stru*,構造一個「假想結構體」位於地址 0。
  2. ((stru*)0)->m:訪問該假想結構體的成員 m,其地址即為 m 在結構體中的偏移量(以位元組為單位)。
  3. &(((stru*)0)->m):取得該偏移量地址(數值等於偏移量)。
  4. (char*)p - (char*)&(((stru*)0)->m):成員實際地址 p 減去偏移量,得出結構體實際首地址。強轉為 char* 確保以位元組為單位運算。
  5. 最終強轉回 stru*,得到結構體指標。

為何用地址 0?

地址 0 為 NULL,不會被合法映射,僅作為編譯期計算偏移量的基準,不產生實際內存訪問。編譯器根據結構體定義直接計算成員偏移,無運行時開銷。

應用場景

  • 內核鏈表:節點嵌入結構體,從 list_head* 反推宿主結構體。
  • 異構對象管理:同一鏈表掛載不同類型結構體,通過成員指標統一訪問。
  • 嵌入式系統:無 RTTI 環境下實現多態與對象關聯。

總結

  • 宏定義:多語句必用 do { ... } while(0) 避免語法陷阱。
  • 通用數據結構:宏或 inline 函數實現類型無關操作。
  • 地址計算:container_of 技巧利用零地址強轉計算偏移,是 C 語言實現面向對象、內核級數據結構的基石。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved