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).