Offsetof

C语言的offsetof()宏,是定义在stddef.h。用于求出一个struct或union数据类型的给定成员的size_t类型的字节偏移值。offsetof()宏有两个参数,分别是结构名与结构内的成员名。不能声明为C原型。

实现
传统实现依赖于编译器对指针不是很挑剔。它假定结构的地址为0,然后获得成员的偏移值:

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

上述定义在C11语言标准下是未定义行为, 因为它对空指针做了解引用(dereference)。GCC现在定义该宏为:

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

这种内置对C++的class或struct也适用。

用途
Linux内核使用offsetof()来实现container_of(),这允许类似于mixin类型以发现包含它的结构:

#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.下述实现也能确保类型安全:

#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限于标准布局类型, 但仍存在未定义行为。特别是虚继承情形。
下述代码用gcc 4.7.3 amd64编译器,产生的结果是有问题的:

#include
#include

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

参考文献

评论 (0)

  • 还没有评论,来抢沙发吧。