contain_of的作用是:已知一个结构体变量的某个成员的地址,怎么获取这个结构体变量的地址。
比如存在如下结构体:
struct Test
{
int a;
int b;
int c;
};
定义一个对应的结构体变量:
struct Test num;
已知num.b的地址,请问怎么拿到num的地址?
如果是人为计算,那么我们知道num的地址其实和结构体成员变量a的地址是同一个。a刚好在b的前面,又是int型,所以a的地址=b的地址-4。然后就可以得到a的地址,也就拿到了num的地址。
其实这里的算法,应用的就是相对地址的概念。即结构体里面的各个成员变量的相对地址是确定的。只要知道了一个成员变量的地址,那么我们就可以根据偏移地址得到其他成员变量的地址。
contain_of的实现原理就是:我们以0地址作为结构体变量的首地址。那么该结构体里面的成员变量的地址就自然变成了相对地址。拿到了成员变量相对地址,再结合结构体变量的地址,就能计算出结构体变量的首地址。举个例子:
struct Test
{
int a;
int b;
int c;
};
struct Test *p=NULL; //定义一个指针
p = (struct Test *)0; //这个指针指向0地址
//那么p->a的地址就是0 计算公式:&(p->a) - 0
//p->a的地址的值就是struct Test结构体里面成员变量a的偏移地址
//那么p->b的地址就是4 计算公式:&(p->b) - 0
//p->b的地址的值就是struct Test结构体里面成员变量b的偏移地址
//那么p->c的地址就是8 计算公式:&(p->c) - 0
//p->c的地址的值就是struct Test结构体里面成员变量c的偏移地址
struct Test num; //定义一个struct Test结构体变量
//假设现在我们已知num.b的地址是0x00000064
//请问num的地址是多少?
//num的地址=num.b的地址-成员变量b的偏移地址
通过上面的介绍,我们了解了contain_of的原理,接下来我们自己来实现contain_of:
#define contain_of_lqd(ptr, type, member) \
((type *)((size_t)ptr - (size_t)&(((type *)0)->member)))
//ptr:结构体变量的成员变量的指针
//type:结构体类型
//member:ptr对应结构体的成员变量名字
//(size_t)ptr:将指针转换成整型
//(type *)0:将0地址转换成结构体变量的地址
//&(((type *)0)->member):取0地址对应的结构体成员变量member的地址
//(size_t)&(((type *)0)->member):将地址转换成整型
完整测试代码:
#include <stdio.h>
#define container_of_lqd(ptr, type, member) \
((type *)((size_t)ptr - (size_t)&(((type *)0)->member)))
struct Test
{
int a;
int b;
int c;
};
int main()
{
struct Test num;
struct Test *p=NULL;
printf("num addr=%p\n", &num);
printf("a addr=%p\n", &num.a);
printf("b addr=%p\n", &num.b);
printf("c addr=%p\n", &num.c);
p = (struct Test *)container_of_lqd(&num.b, struct Test, b);
printf("p addr=%p\n", p);
return 0;
}
我的测试结果:
lqd@ubuntu:~/lqd/c/container_of$ ./a.out
num addr=0x7ffd91c78cf0
a addr=0x7ffd91c78cf0
b addr=0x7ffd91c78cf4
c addr=0x7ffd91c78cf8
p addr=0x7ffd91c78cf0
lqd@ubuntu:~/lqd/c/container_of$