container_of 函数的分析
这个函数的目的是, 通过结构体里面的内容 找到 大结构体的 基地址。
函数的原型是:
PTR是指针
type , member 都是具体的类型。
12 /**11 ▎* container_of - cast a member of a structure out to the containing structure10 ▎* @ptr: the pointer to the member.9 ▎* @type: the type of the container struct this is embedded in.8 ▎* @member: the name of the member within the struct.7 ▎*6 ▎*/5 #define container_of(ptr, type, member) ({ \4 ▎ void *__mptr = (void *)(ptr); \3 ▎ BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \2 ▎ ▎ ▎ !__same_type(*(ptr), void), \1 ▎ ▎ ▎ "pointer type mismatch in container_of()"); \856 ▎ ((type *)(__mptr - offsetof(type, member))); })12 /**
--------------------------------------------------------------------------------------------------------------------------
先来看一个 我自己的 追踪,
接下来看一下 offsetof() 函数
结果是个这个。
再来追踪 __builtin_offsetof() 函数就追踪不到了,这是一个 GCC的函数。
从网上找找这个函数的实现。
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
接下来 解释 一下 这个函数。还是 又不少的东西的。
首先是 这个 : (TYPE *)0)->MEMBER
它的意思是 , 在 TYPE 这个结构体中, 找到 MEMBER成员, 但是 编译器 首先会找到 MEMBER的偏移地址, 并不会 对 0 地址的内存有什么操作 , 先。
然后是: &((TYPE *)0)->MEMBER) , 这意味着 我不是已经找到了MEMBER的的位置了吗, 现在 对这个位置 取地址,你知道,基地址是0 , 所以 MEMBER的地址,就是一个相对地址, 这样我实际上找到的是 TYPE 与MEMBER的差值。但是这个地址值,是有类型的,类型就是 MEMBER* 。
然后就是: ((size_t) &((TYPE *)0)->MEMBER) 我把它强制转换成了一个 int 类型, 这就是一个数字了。
然后就是: __mptr - offsetof(type, member))) 这实际上就是 __mptr 减去一个 int 型的数字,
void *__mptr = (void *)(ptr) 这句说明, __mptr 是一个 void* 的指针。 那么 这句 __mptr - offsetof(type, member))) 就变成了 指针 加减 一个 整数了。
如果是在堆中的话,我们知道,堆是从下往上增长的。
那么 这个 ((size_t) &((TYPE *)0)->MEMBER) 将是一个正数。
那么 __mptr - offsetof(type, member))) 这个 意味着指针的位置 , 在从上往下 减, 也就是从一个小结构体, 找到了一个大结构体的 基地址。
然后就是: ((type *)(__mptr - offsetof(type, member))); 这个函数的 type* 就是 在将 计算出的 大结构体的 指针 (这是一个数字), 转换成 大结构体指针类型,用于寻找 在这个大结构体 中的其他成员。
------------------------------------------------------------------------------------------------------------------------
来看看我自己的测试。
我是在 4412 arm 的裸机程序中做的测试。
71 #define size_t unsigned int72 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)7374 struct human_mod{7576 int head;77 char eye;78 float foot;79 };808182 int main(void)83 {84 size_t ret;85 ret = offsetof(struct human_mod , foot);86 int i = 0;87 led_init ();88 while(1)89 {9091 led_on(i%2);92 led_off(((i-1)+2)%2);93 i++;94 delay_ms(500);9596 }97 return 0;98 }
这是 汇编的结果:
int main(void)
{
40008198: e92d4800 push {fp, lr}
4000819c: e28db004 add fp, sp, #4
400081a0: e24dd008 sub sp, sp, #8
/home/topeet/topeet_wang_4412_for_yikoulinux/yikoulinux_code/led_c/led.c:85size_t ret;ret = offsetof(struct human_mod , foot);
400081a4: e3a03008 mov r3, #8
400081a8: e50b300c str r3, [fp, #-12]
/home/topeet/topeet_wang_4412_for_yikoulinux/yikoulinux_code/led_c/led.c:86int i = 0;
400081ac: e3a03000 mov r3, #0
400081b0: e50b3008 str r3, [fp, #-8]
/home/topeet/topeet_wang_4412_for_yikoulinux/yikoulinux_code/led_c/led.c:87led_init ();
400081b4: ebffff95 bl 40008010 <led_init>
/home/topeet/topeet_wang_4412_for_yikoulinux/yikoulinux_code/led_c/led.c:91while(1){
也就是说 ,汇编的是 已经是结果了,而不是 过程。
这里 直接 把 8 这个数字算出来了。 看来汇编代码 还不底层,更底层的应该是 编译器源码了。
400081a4: e3a03008 mov r3, #8