目录
前言
最近在看《linux设备驱动开发详解》遇到了container_of()宏,在此记录自己对container_of宏定义的理解。
一、container_of(ptr, type, member)宏的作用
该宏的作用是通过结构体成员的地址和结构体类型推导出结构体的地址,type是指结构体的类型,member是成员在结构体中的名字,ptr是该成员在type结构体中的地址。
二、container_of(ptr, type, member)宏解析
在linux源码的tools\include\linux\kernel.h
文件下,container_of()的定义如下:
#ifndef container_of
/**
* 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)); })
#endif
在container_of()
宏的定义中的offsetof(TYPE, MEMBER)
和typeof()
初学者可能会对其很陌生,所以我们要先从理解offsetof(TYPE, MEMBER)
和typeof()
的作用开始。
(1)offsetof(TYPE, MEMBER)
本质也是个宏定义,在linux源码的tools\include\linux\kernel.h
文件下定义如下:
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
offsetof宏中的TYPE是指结构体的类型,MEMBER是指结构体中的某个成员,作用是求出该成员的在该结构体中的偏移量。该宏首先将0
(地址0)转化为TYPE *
的结构体指针,表示地址为0的结构体指针,然后通过取地址符&((TYPE *)0)->MEMBER)
取出该结构体指针中MEMBER成员的地址,最后再将地址值强转为size_t类型(内核中为unsigned long类型)即表示MEMBER成员在结构体中的偏移量。要理解该过程需要了解对结构体的内存分布,如图,结构体的内存分配是连续的,当结构体的地址为0时,成员的地址即为该成员的偏移量。
实例:
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
typedef struct _offset
{
char member_0;
int member_1;
char member_2;
}offset;
int main()
{
printf("%d\n", offsetof(offset, member_0));
printf("%d\n", offsetof(offset, member_1));
printf("%d\n", offsetof(offset, member_2));
return 0;
}
输出:
(2)typeof()
typeof()是GNU C中的一个关键字,和sizeof()一样都是C语言中的关键字而不是函数。作用是返回传入数据的类型。
实例:
#include <stdio.h>
int main()
{
int a = 3;
typeof(a) b = a; /* 求出a变量的类型,并创建一个b变量 */
printf("a=%d b=%d", a, b);
return 0;
}
输出:
(3)container_of(ptr, type, member)
了解了offsetof()宏和typeof关键字之后就比较好理解container_of宏的作用了。
const typeof(((type *)0)->member) * __mptr = (ptr)
该代码的作用实际上是将0
转化为type *
结构体类型,再取出结构体中的MEMBER成员(type *)0)->member
, 再通过typeof关键字获取MEMBER成员的类型,并定义一个MEMBER成员类型的指针const typeof(((type *)0)->member) * __mptr
,将传入的ptr指针赋值给__mptr__mptr = (ptr)
。
(type *)((char *)__mptr - offsetof(type, member));
该代码是将获取的MEMBER成员地址强转为char *
(强转的目的是考虑指针的加减的实质是指针在内存的偏移,偏移量为指针类型所占字节的个数),减去MEMBER成员在type结构体中的偏移量,强转为type *
后得到结构体的地址。
实例:
#include <stdio.h>
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#ifndef container_of
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
#endif
typedef struct _container
{
char member_0;
int member_1;
char member_2;
}container;
int main(void)
{
container *a = NULL;
container b = {'a', 2, 'b'};
/* member_1在实例结构体中的地址 结构体类型 成员名 */
a = container_of(&b.member_1, container, member_1);
printf("a->member_0 = %c\n", a->member_0);
printf("a->member_1 = %d\n", a->member_1);
printf("a->member_2 = %c\n", a->member_2);
return 0;
}
输出: