#define list_entry(ptr, type, member) container_of(ptr, type, member)
在进行编程的时候,我们经常在知道结构体地址的情况下,寻找其中某个成员的地址;但是知道了成员的地址,如果找到这个结构体对应的地址呢?
Linux内核中,获取节点地址的函数是list_entry(),它的宏定义如上所示。
我们再来查找container_of(ptr, type, member)的定义,发现它依然是一个宏定义:
#define container_of(ptr, type, member) \
({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
在container_of(ptr, type, member)的宏定义中,真正返回节点地址的是最后一句话,
而在最后一句话中offsetof(TYPE, MEMBER)依然是一个宏定义。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
typedef __kernel_size_t size_t;
typedef unsigned int __kernel_size_t;
通过逐层查找之后我们来说一下list_entry()函数的具体实现,我们从下往上说起。
-
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
- TYPE
这是我们自定义的结构体类型,它的内部至少一个list_head型成员变量,如下:
struct TYPR
{
//...
struct list_head member;
//...
};
其中list_head也是一个结构体,它的定义我们稍后再说。
- MEMBER
这是TYPE对象中list_head型变量的变量名。
- 语句解析
(TYPE *)0:将0强制转换成TYPE型指针,则该指针一定指向0地址(数据段基址)。
&((TYPE *)0)->MEMBER这句话其实是&(((TYPE *)0)->MEMBER),通过该指针访问TYPE的MEMBER成员并得到其地址。
由于该指针的起始地址是0,那么&((TYPE *)0)->MEMBER也就是一个TYPE型变量的起始地址
与该变量内部MEMBER成员变量起始地址之间的偏移量,这个偏移量对于所有的TYPE型变量都是成立的。
那么,接下来的思路便很明确了,我们只要知道一个TYPE类型变量中MEMBER变量的起始地址,减去
offsetof(TYPE, MEMBER)这个偏移量,就可以得到TYPE类型变量的起始地址。
它的的对应关系如下图所示:
思路很清晰,但还有一些细节需要注意,我们继续看代码。
2.
#define container_of(ptr, type, member) \
({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
- const typeof(((type *)0)->member) * __mptr = (ptr);
由于下面我们要对指针进行强制类型转换,所以这里我们又申请一个指针,指向和ptr相同的位置。
这里的ptr指的是实际list_head member的地址。
- (char *)__mptr
由于offsetof()函数求得的是偏移字节数,所以这里(char *)__mptr使得指针的加减操作步长为1Byte
然后二者相减便可以得到TYPE变量的起始地址,最后通过(type *)类型转换,将该地址转换为TYPE类型的指针。
再向上就是一些宏定义没有什么可说的了。