offsetof
C语言的offsetof()宏,是定义在stddef.h。用于求出一个struct或union数据类型的给定成员的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++的class或struct也适用。[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
参考文献
- ^ offsetof reference. MSDN. [2010-09-19]. (原始内容存档于2011-10-10).
- ^ Does &((struct name *)NULL -> b) cause undefined behaviour in C11?. [2015-02-07]. (原始内容存档于2015-02-07).
- ^ GCC offsetof reference. Free Software Foundation. [2010-09-19]. (原始内容存档于2010-07-24).
- ^ what is the purpose and return type of the __builtin_offsetof operator?. [2012-10-20]. (原始内容存档于2014-12-15).
- ^ Greg Kroah-Hartman. container_of(). Linux Journal. June 2003 [2010-09-19]. (原始内容存档于2010-02-13).
- ^ Statements and Declarations in Expressions. Free Software Foundation. [2016-01-01]. (原始内容存档于2016-01-05).
- ^ offsetof reference. cplusplus.com. [2016-04-01]. (原始内容存档于2016-03-30).
- ^ Steve Jessop. Why can't you use offsetof on non-POD structures in C. Stack Overflow. July 2009 [2016-04-01]. (原始内容存档于2019-10-19).