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.
作者能力有限,如發現錯誤歡迎指正。