1. 问题描述:
现在定义了一个结构体:
struct Foo
{
int a;
int b;
};
Foo foo;
假如由于函数传参等原因,现在程序只能拿到 foo.b 的地址,这时想通过某种方法获取到 foo 结构体里的其他成员。
那么问题来了,这就是以下主要讨论的内容。
2. 原理概述
将地址 0 强制转换成一个结构体指针,伪代码: struct foo *p = (struct Foo *)0;
从而通过已知的结构体成员的地址减去结构体的首地址得到已知结构体成员的内存偏移 , 伪代码 : offset = &(p->b) - p;
那么问题就简单了, 现在已知 foo.b 的地址,将其减去偏移即可得到该结构体的首地址。
3. 实现举例
//file name : list_head.c #include <stdio.h> struct list_head { struct list_head *next; }; struct fox { unsigned int tail_color; unsigned int tail_length; struct list_head list; }; #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER ) #define container_of(ptr, type, member) ({\ const typeof( ((type *)0)->member)* __mptr = (ptr);\ (type *)((char*)__mptr - offsetof(type, member));}) int main(int argc, char** argv) { unsigned short offset = 0; struct fox *p = 0; struct fox red_fox; red_fox.tail_color = 45; red_fox.tail_length = 10; unsigned int *p_t; printf("red_fox_addr: %x\n", &red_fox); printf("tail_color_addr: %x\n", &(red_fox.tail_color)); printf("tail_length_addr: %x\n", &(red_fox.tail_length)); printf("list_addr: %x\n", &(red_fox.list)); // offset = (unsigned char*)&(p->list) - (unsigned char*)p; offset = offsetof(struct fox, list); printf("offset: %d \n", offset); p_t = container_of(&red_fox.list, struct fox, list); printf("red_fox_addr: %x\n", p_t); printf("tail_color: %d \n", ((struct fox *)p_t)->tail_color); printf("tail_length: %d \n", ((struct fox *)p_t)->tail_length); return 0; }
4. 应用
Linux 中数据结构单链表使用的这种方法。好处也是显而易见的,当用户想通过单链表实现自己封装的数据结构时不需要在单独结构体定义单链表遍历的指针和相关函数,仅仅实现包含 list_head 这个结构体成员即可。而内核提供了完整且高效的用于单链表操作 api.
作者能力有限,如发现错误欢迎指正。