C語言offsetof()巨集,是定義在stddef.h。用於求出一個structunion資料類型的給定成員的size_t類型的位元組偏移值。offsetof()巨集有兩個參數,分別是結構名與結構內的成員名。不能聲明為C原型。[1]

實現 編輯

傳統實現依賴於編譯器對指標不是很挑剔。它假定結構的位址為0,然後獲得成員的偏移值:

#define offsetof(st, m) ((size_t)&(((st *)0)->m))

上述定義在C11語言標準下是未定義行為[2] 因為它對空指標做了解除參照(dereference)。GCC現在定義該巨集為:[3]

#define offsetof(st, m) __builtin_offsetof(st, m)

這種內建對C++的classstruct也適用。[4]

用途 編輯

Linux核心使用offsetof()來實現container_of(),這允許類似於mixin類型以發現包含它的結構:[5]

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

container_of()巨集被用於從指向內嵌成員的指標獲得外包的結構的指標。如鏈結串列my_struct:

struct my_struct {
    const char *name;
    struct list_node list;
};

extern struct list_node * list_next(struct list_node *);

struct list_node *current = /* ... */
while(current != NULL){
    struct my_struct *element = container_of(current, struct my_struct, list);
    printf("%s\n", element->name);
    current = list_next(&element->list);
}

Linux核心實現container_of()時,使用了GNU C擴充statement expressions.[6]下述實現也能確保類型安全:

#define container_of(ptr, type, member) ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - offsetof(type, member)))

粗看起來,上述的不尋常的?:條件運算子是不適當的。可以寫成更簡單的形式:

#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))

這種寫法忽略了檢查ptr的類型是否是member的類型,而Linux核心的實現需要這種安全檢查。而?:條件運算子要求如果運算元是一個類型的兩個指標值,那麼它們應當是相容的類型。所以第三個運算元雖然不會被使用,但編譯器要檢查(ptr)&((type *)0)->member是不是相容的指標類型。

局限性 編輯

C++03要求offsetof限於POD類型。C++11要求offsetof限於標準布局類型[7] 但仍存在未定義行為。特別是虛繼承情形。[8] 下述代碼用gcc 4.7.3 amd64編譯器,產生的結果是有問題的:

#include <stddef.h>
#include <stdio.h>

struct A
{
    int  a;
    virtual void dummy() {}
};

struct B: public virtual A
{
    int  b;
};

int main()
{
    printf("offsetof(A,a) : %zu\n", offsetof(A, a));
    printf("offsetof(B,b) : %zu\n", offsetof(B, b));
    return 0;
}

Output is:

offsetof(A,a) : 8
offsetof(B,b) : 8

參考文獻 編輯

  1. ^ offsetof reference. MSDN. [2010-09-19]. (原始內容存檔於2011-10-10). 
  2. ^ Does &((struct name *)NULL -> b) cause undefined behaviour in C11?. [2015-02-07]. (原始內容存檔於2015-02-07). 
  3. ^ GCC offsetof reference. Free Software Foundation. [2010-09-19]. (原始內容存檔於2010-07-24). 
  4. ^ what is the purpose and return type of the __builtin_offsetof operator?. [2012-10-20]. (原始內容存檔於2014-12-15). 
  5. ^ Greg Kroah-Hartman. container_of(). Linux Journal. June 2003 [2010-09-19]. (原始內容存檔於2010-02-13). 
  6. ^ Statements and Declarations in Expressions. Free Software Foundation. [2016-01-01]. (原始內容存檔於2016-01-05). 
  7. ^ offsetof reference. cplusplus.com. [2016-04-01]. (原始內容存檔於2016-03-30). 
  8. ^ Steve Jessop. Why can't you use offsetof on non-POD structures in C. Stack Overflow. July 2009 [2016-04-01]. (原始內容存檔於2019-10-19).