就内核的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()函数,在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字节对齐;
结构体在内存中地址图示(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(ptr, type, member)函数的作用:由结构体成员member的地址ptr,求出结构体type的起始地址。
其参数:
* @ptr: 指向成员member的指针(或member的址址)
* @type: 包含成员member的结构体类型
* @member: 成员的名字
由图示更好理解:
container_of()函数所实现的就是求出图中的“?”,即结构体的起始地址。
已知成员member的地址ptr,求结构体的首地址,只需知道ptr与结构体首地址的偏移量,而这恰恰就是offsetof()宏函数了。
那么,结构体的首地址 = ptr - offsetof(type,member) ,加上类型转换等优化,变成:
(type *)(ptr - offsetof(type,member) );
就这样,收工?
与container_of()原定义有点出入,其多出一行:
const typeof( ((type *)0)->member ) *__mptr = (ptr);
typeof()函数:获取变量(member)的类型;
这行的意思:定义一个与member同类型的指针__mptr指向ptr,后面用__mptr而不直接用ptr了。
这行好像是多此一举,并无大用???
原因看这里:若container_of()的参数ptr有误,即ptr与member的类型不匹配,编译时便会提示warnning信息,以引起注意;若无此语句,便无warning引起注意,后果可大可小!!!这便是编程的严谨性体现之处了。
注意到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(),如内核链表中就有应用。
希望此博文能对你有积极影响,水平有限,欢迎指点。