Linux C: container_of()和offsetof()函数与结构体成员的偏移量计算

就内核的container_of()函数,发表一下个人见解。

在内核中,多处地方用到container_of()这个函数(如内核链表),

其定义在内核代码 include/linux/kernel.h中:

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:        the pointer to the member.
 * @type:       the type of the container struct this is embedded in.
 * @member:     the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

作用---根据结构体成员member的地址ptr,求出结构体type的起始地址。

它是如何实现的呢?下面一步一步来拆解。

下面一步一步来拆解。

 

一、offsetof()函数

其中的offsetof()函数,在include/linux/stddef.h中,定义如下:

#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#endif /* __KERNEL__ */

只需看这行:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

这是一个宏函数,其作用---求成员MEMBER在结构体TYPE中的偏移

(TYPE *)0这种奇葩写法是什么意思呢?

如有以下结构体:

struct demo
{
	char 	c;
	short 	s;
	int 	i;
};

其中各成员的对齐方式如下:(有疑问者请自行了解结构体字节对齐规则)

char - 1字节对齐;short - 2字节对齐;int - 4字节对齐;

Linux C: container_of()和offsetof()函数与结构体成员的偏移量计算_第1张图片

结构体在内存中地址图示(p为结构体的首地址)

由图可知:某成员相对结构体的偏移=该成员地址-结构体首地址,例:偏移量(s) = 地址(s) - p

由此,想象将该结构体整体移到0地址处呢?

那么p=0,成员偏移量=成员的址址,问题秒简化。

如何将结构体整体移至0地址处?--- 将0地址进行类型强转换,即(struct demo *)0,这样是虚拟0地址处有struct demo结构体,只可读而不可写,因为C语言是允许在任何地址进行任何类型转换,读动作是没问题的,而写动作责要谨慎且后果自负。

由上,成员s的偏移量=s的地址:&((struct demo *)0)->s,再将其强转化为size_t类型:

(size_t)&((struct demo *)0)->s;

这样,是不是就跟offsetof()这个宏函数一样了。

 

二、container_of()函数

1、逻辑图示

container_of(ptr, type, member)函数的作用:由结构体成员member的地址ptr,求出结构体type的起始地址。

其参数:

 * @ptr:      指向成员member的指针(或member的址址)
 * @type:    包含成员member的结构体类型
 * @member:    成员的名字

由图示更好理解:

Linux C: container_of()和offsetof()函数与结构体成员的偏移量计算_第2张图片

container_of()函数所实现的就是求出图中的“?”,即结构体的起始地址。

已知成员member的地址ptr,求结构体的首地址,只需知道ptr与结构体首地址的偏移量,而这恰恰就是offsetof()宏函数了。

那么,结构体的首地址 = ptr - offsetof(type,member) ,加上类型转换等优化,变成:

(type *)(ptr - offsetof(type,member) );

就这样,收工?

 

2、优化/学习/严谨

与container_of()原定义有点出入,其多出一行:

const typeof( ((type *)0)->member ) *__mptr = (ptr);

typeof()函数:获取变量(member)的类型;

这行的意思:定义一个与member同类型的指针__mptr指向ptr,后面用__mptr而不直接用ptr了。

这行好像是多此一举,并无大用???

原因看这里:若container_of()的参数ptr有误,即ptr与member的类型不匹配,编译时便会提示warnning信息,以引起注意;若无此语句,便无warning引起注意,后果可大可小!!!这便是编程的严谨性体现之处了。

 

3、宏定义小知识

注意到container_of()宏函数的外围是用({ })双括号包起来的,这有什么用呢?

首先,要明白宏定义只是在预处理阶段作简单的文本替换。

()小括号:

()的运算优先级高,能将此宏包起来作为一个整体而不被拆分,以免造成意料之外的影响。一般宏定义含多级运算或多表达式等都建议用()包起来。

 

{}大/花括号:

其作用较复杂,(个人观点,欢迎纠正)

1、语法要求:假如去掉{},调用container_of()则变成:

(
    const typeof( ((type *)0)->member ) *__mptr = (ptr);
    (type *)( (char *)__mptr - offsetof(type,member) );
)

细数()是否成对出现,发现一对()会被“ ; ”隔断,犹如:( foo(); ) 这样,语法上是不允许一对()被“ ; ”隔断的。加上{}则表示这应当成一个整体(复合语句)。

2、复合语句:{}将多个语句包起来,在程序中应看作为一条语句执行,且以最后一条语句的执行结果作为其值,可用于赋值。

3、限定作用域:{}里面定义的变量只在{}中有效,若无{}且多次调用该宏,则会导致变量被重复定义。

4、形式作用:宏函数最好要有函数的样子,就是用{}包起来表示,使更像函数。

 

三、结构体偏移量的应用

 结构体中的偏移量相关应用,

1、某成员在结构体中的相对偏移:参考offsetof()函数,巧用0地址指针,如(size_t)&((struct demo *)0)->s,访问某成员时可避免写死相对偏移量,便于修改/维护/移植等。

2、知道结构体某成员的地址,想要得到结构体的首地址:参考container_of(),如内核链表中就有应用。

 

希望此博文能对你有积极影响,水平有限,欢迎指点。

 

你可能感兴趣的:(linux,C)