在linux源码中经常会使用用成员指针得到当前结构体变量的指针,就是使用container_of,在定位源代码会看到如下:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* 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)); })
这里面offsetof就是定位成员变量的偏移,假设有一个结构体,如下
typedef struct _LinkList
{
LNode* pHead; //指向头结点
LNode* pTail; //指向尾结点
int len; //链表的长度
}LinkList, *pLinkList;
将上面的宏展开,如下:
offsetof(LinkList, len) (size_t) &((LinkList*)0)->len //这里就是取得len成员变量的地址
这里默认以0地址作为此结构的强制默认内存空间来计算偏移地址,这里使用size_t主要考虑不同机器指针所占用大小不一
再看一下container_of这里面有3个成员,根据内核注释,这里:
ptr:就是结构体成员指针,这里地址是相对应与内存空间的首地址
type:就是为了获得的结构体类型
member:是结构体成员哪一个变量,其实可以直接理解为相对于结构体0地址的偏移地址
展开上面的宏得到如下实例,还使用上面的例子:
container_of(&nLen, LinkList, len)
{
const typeof(((LinkList *)0)->len)*__mptr = (&nLen);
(LinkList *)((char *)__mptr - offsetof(LinkList, len));
}
这里先理解一下大意,就是说先检查数据类型的检查,然后获得就是结构体变量的地址
看到这里面有个typeof,这个获得所属变量,表达式,函数的类型等,typeof构造的主要应用是用在宏定义中。可以使用typeof关键字来引用宏参数的类型。因此,在没有将类型名明确指定为宏实参的情况下,构造带有所需类型的对象是可能的。
下面是一个交换两个变量的值的宏定义:
#define SWAP(a,b) {\
typeof(a) t=a;\
a=b;\
b=t;}
这个宏可以交换所有基本数据类型的变量(整数,字符,结构等)
这里面还有个__mptr,神马意思?在论坛中找到的解释,如下:
观点一:这里使用__mptr是为了防止不小心修改ptr指向的值,由于形参没有修饰符,如果不小心修改ptr所指向的值,编译器可能不会报错,所以引入__mptr并加上const修饰符,在后面如果不小心修改了指向的值就会报错,一种对源数据进行保护的机制。
观点二:引入__mptr是为了作编译期间的类型检查,确保传入的ptr是指向struct的指针。而在运行期间,这个赋值gcc可以优化掉,不会影响效率。
其实仔细理解就明白,const typeof(((LinkList *)0)->len)* 这里就是一个类型,因为typeof取得是一个成员变量的类型,后面有个*说明是个指针类型,const是修饰这个类型的,所以指针指向的内容是不能改变的
注意:const type * p 与 type * const p ,前面指向的内容不能修改,后面的是指针的值不能修改
那么以上就很好理解了,其实这里的确是做类型检查,__mptr就是一个临时变量
下面一句:(LinkList *)((char *)__mptr - offsetof(LinkList, len)); 就是用成员变量的地址减去成员变量相对于结构体的偏移量,就得到结构体的首地址了
这里注意一点,因为如果默认情况没有返回值,宏的最后一个表达式的值就是返回值。
内存空间地址顺序应该相反的,意思就是用len的地址减去距离首地址的偏移就得到结构体成员的首地址了,从这里我们也应该能明白强制转换其实只是告诉编译器读取的时候按照这么一块内存的大小来读取或者写入,看一下内核中使用这个宏的例子:如下
struct device *dev = container_of(kobj, struct device, kobj);
这里第一个kobj是传入的形参,后一个为成员变量,device的定义如下
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
....
这里有一个kobj的成员变量,其实kobject好多成员都封装了,它与kset构筑了linux的骨架或者说基石,仔细看一下这两个结构的定义和使用就明白
注意:以上属个人拙见,不足之处,欢迎指正