在写Linux驱动的过程中经常是一个结构体套一层结构体,而在某些函数中传入的参数是子结构体指针,但是我们又需要获取的其外层结构体的数据,Linux为我们提供了container_of宏定义来为我们解决这个问题。
container_of宏定义就是用来通过内层结构体的指针获取外层结构体指针,宏定义很巧妙,我等凡人想不出来。下面就详细分析其实现原理吧。
container_of
宏定义需要用到offsetof
宏,现在offsetof
宏的功能已经变成一个GCC的内建函数了__builtin_offsetof (TYPE, MEMBER)
,但offsetof宏实际定义可由第二行定义实现。
#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
#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) );})
offsetof(TYPE, MEMBER)
的操作过程:
首先分析offsetof宏,其功能是获得成员MEMBER
在TYPE
结构中的偏移量。
- 宏参数
TYPE
:表示MEMBER成员所在结构体的数据类型; - 宏参数
MEMBER
:TYPE结构体的数据成员MEMBER; - 根据运算符的优先级最内层的
((TYPE *)0)
:表示将0地址转换为TYPE类型的指针 ((TYPE *)0)->MEMBER
:通过0地址,类型为TYPE的指针,指向成员MEMBER&((TYPE *)0)->MEMBER
:对MEMBER成员进行取地址运算,那么地址值就是MEMBER成员在TYPE结构中的偏移量(size_t)&((TYPE *)0)->MEMBER
:将地址值强制转为size_t类型的整形数值,即偏移量。
container_of(ptr, type, member)
的操作过程:
- 首先说明关键字
typeof
,可以获得一个数据的类型 - 宏参数
ptr
:表示指向member成员的指针; - 宏参数
type
:表示member成员所在结构体的数据类型; - 宏参数
member
:type结构体的数据成员member; - container_of宏被封装成一个语句块,包含两条语句,define预定义返回最后一句话的值
- 第一句:
const typeof( ((type *)0)->member ) *__mptr = (ptr);
- 根据运算符的优先级最内层的
((type *)0)
:表示将0地址转换为type
类型的指针 ((type *)0)->member
:将0地址指针指向member
成员typeof( ((type *)0)->member )
:获得member
的数据类型const typeof( ((type *)0)->member ) *__mptr
:定义一个__mptr
指针变量,指针数据类型为member
的数据类型const typeof( ((type *)0)->member ) *__mptr = (ptr);
:将宏参数变量ptr
赋值给变量__mptr
;此时变量__mptr
中保存的是member
成员的地址- 第二句:
(type *)( (char *)__mptr - offsetof(type, member) );
- 将
__mptr
强制转换为char*
指针 ( (char *)__mptr - offsetof(type, member) )
:用强制转换后的指针值减去member
在type
中的偏移量,得到的值就是member
成员所在结构体的地址,此时仍然是char*
指针(type *)( (char *)__mptr - offsetof(type, member) );
将最后的得到的地址转换为(type *)
指针,即得到member
成员所在结构体的指针。
仔细分析会发现,其实根本没有必要定义第一句,只需一句就可以实现:
#define container_of(ptr, type, member) ({ \
(type *)( (char *)(ptr) - offsetof(type,member) );})
但是为什么会定义第一句呢?其实主要为了对ptr
与member
做类型检查,如果用typeof
求出来的类型和ptr
不一致,那么在赋值操作时,编译器会报错。
可能会有仁兄有疑问:0地址访问数据,不会出现非法地址访问的错误吗?
仔细分析,这里并没有发生0地址的附近数据的访问,仔细看看仅仅发生了&
取地址、typeof
取类型操作,因此不会存在非法访问内存的问题。
新的内核对此又有了新的定义:
/* Are two types/vars the same type (ignoring qualifiers)? */
#ifndef __same_type
# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#endi
#define container_of(ptr, type, member) ({ \
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \
!__same_type(*(ptr), void), \
"pointer type mismatch in container_of()"); \
((type *)(__mptr - offsetof(type, member)));
有2个新变化:
第一,用void *
取代了char *
来做减法,
第二,用__same_type
宏来做类型检测,错了有明确的信息提示。
好了,以后继续把链表相关的操作也搬上来。