container_of是linux中的一个宏,它的作用是通过结构体中某一成员的地址来获得该成员所在结构体的地址。其定义在include/linux/kernel.h中653行,如下所示:
下面对上述代码进行简单的介绍:对于第653行,该行定义了宏名,并且该宏包含三个参数:ptr用于存放结构体中某已知成员的地址;type用于指明该结构体的名称;member用于存放ptr所对应的成员在结构体中的名称。对于第654行,它的本质是定义了一个变量,并对其赋值。其中typeof是gcc对c语言的扩展保留字,用于获得变量的类型。此时该语句就不难理解:这里首先将强制转化为该结构体类型的指针,然后通过该指针指向相应的成员变量,接着再通过typeof获取该成员变量的类型。所以这行代码的意思就是定义一个与结构体成员变量member同类型的指针变量,并将ptr赋值给它。对于第655行,由于ptr存放的是结构体中某成员的地址,所以_mptr存放的也就是该成员的地址;offsetof用于获得结构体成员member相对于结构体起始地址的偏移量,所以该结构体的地址就应该为该成员变量的地址减去其相对于结构体起始地址的偏移量。
宏offsetof用于获得结构体中某一成员相对于结构体起始地址的偏移量。它定义在include/linux/stddef.h中第24行:
其中(TYPE *)0 是骗编译器说有一个指向该结构体TYPE的指针,其值为;&((TYPE *)0)->MEMBER用于获得结构体中成员变量MEMBER的地址,由于该结构体类型的变量的基地址为,此时MEMBER的地址就是其相对于结构体的偏移量;然后再将结果强制转化为size_t,其在标准c库中定义为unsigned int。
下面举例进行说明该宏的用法(由于该宏是在内核态定义的,用户态无法使用,这里将该宏的定义拷贝到用户态进行使用):
程序一:
#include <stdio.h>
#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr,type,member)({ \
const typeof(((type *)0)->member)* _mptr=(ptr); \
(type *)((char *)_mptr-offsetof(type,member));})
struct student{
char *num;
char *name;
int age;
};
int main()
{
struct student stu={"0001","zhang",10};
struct student *s=container_of(&(stu.name),struct student,name);
printf("name=%s,sex=%s,age=%d\n",s->num,s->name,s->age);
return 0;
}
程序二:
#include <stdio.h>
#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr,type,member)({ \
const typeof(((type *)0)->member)* _mptr=(ptr); \
(type *)((char *)_mptr-offsetof(type,member));})
struct student{
char num[5];
char name[10];
int age;
};
int main()
{
struct student stu={"0001","zhang",10};
struct student *s=container_of(stu.name,struct student,name);
printf("num=%s,name=%s,age=%d\n",s->num,s->name,s->age);
return 0;
}
对上述程序分别进行编译,其结果如下图所示,其中图一为程序一的编译结果;图二为程序二的编译结果。
图一 程序一的编译结果
图二 程序二的编译结果
分析:为什么程序二的编译会出现警告呢(上述警告的意思为指针赋值时类型不兼容)?原因很简单,下面分别将该宏展开如下所示:
程序一:
#define offsetof(struct student,name) ((size_t)&((struct student *)0)->name)
#define container_of(&(stu.name),struct student,name)({ \
const typeof(((struct student *)0)->name)* _mptr=(&(stu.name)); \
(struct student *)((char *)_mptr-offsetof(struct student,name));})
程序二:
#define offsetof(struct student,name) ((size_t)&((struct student *)0)->name)
#define container_of(stu.name,struct student,name)({ \
const typeof(((struct student *)0)->name)* _mptr=(stu.name); \
(struct student *)((char *)_mptr-offsetof(struct student,name));})
对于程序段一中的第三行,这段代码的意思是定义一个char类型的二级指针_mptr。因为student struct结构体中name成员为char类型的一级指针,通过typeof后便得到其类型,然后再定义一个指向字符指针类型的指针_mptr;接着将stu.name的地址赋值给_mptr。由于stu.name为字符指针类型,取地址后即为二级字符指针。综上知,编译时不会出现警告信息。
对于程序段二中的第三行,这段代码的意思也是定义一个char类型的二级指针_mptr。其中大部分都和程序段一中的相同,但在赋值时,由于stu.name为指向一个长度为10的字符数组的指针常量,所以类型不匹配,因此在编译时会产生警告信息。在该程序中,如果将container_of宏的第一个参数设置为&(stu.name),在编译时同样会出现上述警告。此时只需将该宏的第二行中的const关键字删除即可,删除后编译将不会出现警告。对于发生这种错误的原因现在我还不清楚,有待于以后的探究。