在Linux内核中有两个比较常用的宏: offsetof 和 containter_of
参考自 狄泰 <<数据结构>> 课程
这里先给出这 offsetof 宏的定义
offsetof 宏:
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER)
#endif
这个宏是用于计算 TYPE 类型中 MEMBER 成员的偏移量 , size_t 类型是 unsigned int 类型
怎么计算?
这里使用 0 转换为 TYPE 类型再取 MEMBER 成员再转换类 size_t 类型.
将 0 转换成 TYPE 类型后 首地址就变成了从 0 开始, 那么每个成员的类型大小
就会是该成员在结构体的偏移量
这里有个问题: 直接使用 0 地址不会发生程序崩溃吗?
首先编译器在编译期间就已经知道结构体成员的偏移量: 编译器通过取结构体首地址与偏移量定位成员
这里要注意编译器只是取结构体首地址 并不会操作首地址,所以并不会造成程序在运行态的崩溃
containter_of 宏:
#ifndef container_of
#define container_of(ptr, type, member)({ \
const typeof(((type*)0)->member)* __mptr = (ptr); \
(type*)((char*)__mptr - offsetof(type, member));})
#endif
这个宏的作用就是通过结构体的成员反推该结构体的首地址
分析这个宏之前要先说下这里面包含的一个语法知识: ({ })
这个是 GNU C编译器扩展出来的语法,所以在其他标准的 C编译器不一定能编译得过
**({ })**与逗号表达式类似 结果为最后一个语句的值
唯一区别就是逗号表达式是不能存在分号 而 ({ }) 表达式是 使用分号
int a = 0;
int b = 0;
int c = ( <==> int c = ({
a = 1, int a = 1;
b = 2, int b = 1;
a + b a+b;
); });
第二个知识点就是 typeof 关键字 :
这个关键字也是GNU C编译器特有的关键字
而且这个关键字 只在编译期生效,
用于得到变量的类型
int i = 10;
typeof(i) j = i;
const typeof(i)* p = j;
printf("j = ", j);
printf("p = ", *p);
container_of 宏 最关键的语句就是
(type*)((char*)__mptr - offsetof(type, member));
这里 __mptr 指针就是指向传入的结构体成员的指针, 通过该指针指向的地址值减去该成员的偏移量
就得到结构体的首地址
那么这里就有个问题: 既然一条语句可以解决为什么还需要第一条语句用一个新的指针指向传入的指针呢
其实这个宏最精妙的就是第一条语句:
const typeof(((type*)0)->member)* __mptr = (ptr);
这条语句的作用就是 类型检查 这里使用 typeof 获取 member 的类型后 再指向 ptr
这样做 当传入的 指针 ptr 与member 的类型不同,这样编译器就会发出警告.