1. 简介
container_of
宏出现在 Linux 内核链表的管理程序中,其主要功能是通过结构体变量的任一成员便可得到该结构体变量的首地址。这是由于 Linux 内核链表被封装为一个单纯的链表结构类型,只要在结构体中定义了该链表结构类型的成员变量,该结构体类型便成为一个链表类型。因此,在链表中使用该宏可以快速地通过链表结构类型的结构体成员定位到成员所属的节点的地址!
container_of
宏源码为:
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type,member));})
其中,ptr
为指向已知的结构体变量的成员的指针,type
为结构体的数据类型,member
为结构体成员名称。
offsetof
也为一个宏,源码为:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
我相信绝大部分人没有见识过如此奇异的语法,如果有人能够一眼看懂这两个宏,那他的 C 语言修炼绝对是大师级别的。但大部分人还是看得是云里雾里的,根本不知所云,只能上网求助。本文我将通过原理结合实验的方法,带领大家一一解剖 container_of
宏!
2. 准备知识
看不懂宏里的语法,归根到底还是基础知识的欠缺,所以接下来好好补充一下 C 中的高阶知识吧!
2.1 双括号语法
在 container_of
宏中有两条明显的语句,其中第二条语句仅为一句减法语句,虽然对差值进行了强制类型转换,但并没有变量接收该差值,非常“诡异”!同时,宏的功能既然是得到结构体变量的首地址,则宏应该返回的是一个数值,但这里有两条完整的语句,宏替换后又怎么返回地址值呢?也非常“诡异”!
其实,这些“诡异”的状况都是双括号语法造成的。双括号语法为 GNU C 扩展的语法,形为 ({...})
,在双括号体内允许包含任意多的完整的语句,并规定最后一条语句的结果便是该双括号体的返回值。
文字描述可能有些晦涩,那就直接通过程序体会体会!
- 实验:
int main()
{
int var = ({
int tmp = 5;
tmp * tmp;
});
printf("var = %d\n",var); // var = 25
}
从实验可以看出,var
的初始化实际等价于 var = tmp * tmp;
,即双括号体的返回值为最后一条语句的结果!
2.2 typeof 关键字
typeof 关键字为 GNU C 扩展的关键字,作用是提取修饰变量的数据类型,并把该数据类型替换到关键字所修饰的地方。例如:对于 int 型变量 num
,typeof(num)
将被 “int” 替换掉。
- 实验:
int main()
{
char