在linux内核链表中,会遇到两个宏。
在include/linux/stddef.h中,有这样的定义
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这里的TYPE表示某个结构体类型,MEMBER表示结构体中的一个成员,这个宏求出了成员在结构体中的位置偏移(以字节为单位)
如果你还不理解,我们举个例子吧。
struct student { char name[20]; unsigned char age; }; int main(void) { int off = offsetof(struct student, age); printf("off = %d ",off); }运行结果是off = 20
其实宏里面的0只是一种特殊情况而已。对这个宏的解释是:假设结构体处于0x1234这个地址,
((TYPE*)0x1234 )-> MEMBER
->比类型转换的优先级高,所以要加括号。上面就得到了成员,再取地址,得到成员的地址
&((TYPE*)0x1234 )-> MEMBER
不用加括号,因为&的优先级比较低
再来一个类型转换
(size_t)&((TYPE*)0x1234 )-> MEMBER
终于得到成员的起始地址了,还没有完,既然算偏移,就要减去结构体的起始地址
(size_t)&((TYPE*)0x1234 )-> MEMBER - 0x1234
不难看出,这里的0x1234换成什么数字都可以,因为偏移是和起始地址无关的。
不信的话可以把这个宏定义改一改,再测试一下
#define offsetof(TYPE, MEMBER) ( (size_t) &((TYPE *)0x2222)->MEMBER - 0x2222 ) struct student { char name[20]; unsigned char age; }; int main(void) { int off = offsetof(struct student, age); printf("off = %d ",off); }改成0x2222后,结果还是20.
既然什么数字都可以,那就改成0吧,于是后面的-0就可以省略了。于是就得到了开头的那个宏。
下面我们说另外一个宏。
/**
827 * container_of - cast a member of a structure out to the containing structure
828 * @ptr: the pointer to the member.
829 * @type: the type of the container struct this is embedded in.
830 * @member: the name of the member within the struct.
831 *
832 */
833 #define container_of(ptr, type, member) ({
834 const typeof( ((type *)0)->member ) *__mptr = (ptr);
835 (type *)( (char *)__mptr - offsetof(type,member) );})
继续举例子。
struct student { char name[20]; unsigned char age; }; int main(void) { struct student stu = {"wangdong",22}; printf("&stu = %p ",&stu); printf("&stu.age = %p ",&stu.age); struct student *p = container_of(&stu.age, struct student, age); printf("p= %p ",p); }运行结果为
&stu = 0x7fff53df9c40
&stu.age = 0x7fff53df9c54
p= 0x7fff53df9c40
const typeof( ((type *)0)->member ) *__mptr = (ptr);
没有这行到底行不行,其实也行,用上面的例子,去掉这行,也可以得到一样的结果。
先看看这行什么意思吧, ((type *)0)->member 这是成员,typeof( ((type *)0)->member ) 得到了成员的类型,假设就是unsigned char类型,
const typeof( ((type *)0)->member ) *__mptr 定义了一个这个类型的指针
const unsigned char * __mptr = (ptr); 赋值给定义的这个指针。
我苦思冥想,又结合网上的资料,认为这样写是做了一个类型检查。如果有了这行,假设结构体就没有member这个成员,那么编译会报错。
所以这样写保证了member确实是type的一个成员。
还有,这样写也保证了ptr确实是这个成员类型的指针,如果不是,编译也会报错。再做个实验。
把刚才的代码改一下
struct student *p = container_of((int *)&stu.age, struct student, age);
故意把&stu.age转换成int*,编译就会报警告:
test.c:33:22: warning: incompatible pointer types initializing 'const typeof (((struct student *)0)->age)
*' (aka 'const unsigned char *') with an expression of type 'int *' [-Wincompatible-pointer-types]
struct student *p = container_of((int *)&stu.age, struct student, age);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.c:11:39: note:expanded from macro 'container_of'
const typeof( ((type *)0)->member ) *__mptr = (ptr);
^ ~~~~~
所以,作者的出发意图是千方百计地让程序员写出安全的代码啊!真的是用心良苦。
(完)