Linux 宏定义container_of详解

在写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宏,其功能是获得成员MEMBERTYPE结构中的偏移量。

  • 宏参数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) ):用强制转换后的指针值减去membertype中的偏移量,得到的值就是member成员所在结构体的地址,此时仍然是char*指针
  • (type *)( (char *)__mptr - offsetof(type, member) );将最后的得到的地址转换为(type *)指针,即得到member成员所在结构体的指针。

仔细分析会发现,其实根本没有必要定义第一句,只需一句就可以实现:

#define container_of(ptr, type, member) ({ \

(type *)( (char *)(ptr) - offsetof(type,member) );})

但是为什么会定义第一句呢?其实主要为了对ptrmember做类型检查,如果用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宏来做类型检测,错了有明确的信息提示。

好了,以后继续把链表相关的操作也搬上来。

猜你喜欢

转载自blog.csdn.net/zhaoyun_zzz/article/details/85991298