一、#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER )
1. ( (TYPE *)0 ) 將零轉型為TYPE類型指針;
2. ((TYPE *)0)->MEMBER 訪問結構中的數據成員;
3. &( ( (TYPE *)0 )->MEMBER )取出數據成員的地址;
4.(size_t)(&(((TYPE*)0)->MEMBER))結果轉換類型;
巧妙之處在於將0轉換成(TYPE*),結構以內存空間首地址0作為起始地址,則成員地址自然為偏移地址。
舉例說明:
#include<stdio.h>
typedef struct _test
{
char i;
int j;
char k;
}Test;
int main()
{
Test *p = 0;
printf("%p\n", &(p->k));
}
答案:00000008
自己分析:這裡使用的是一個利用編譯器技術的小技巧,即先求得結構成員變量在結構體中的相對於結構體的首地址的偏移地址,然後根據結構體的首地址為0,從而得出該偏移地址就是該結構體變量在該結構體中的偏移,即:該結構體成員變量距離結構體首的距離。在offsetof()中,這個member成員的地址實際上就是type數據結構中member成員相對於結構變量的偏移量。對於給定一個結構,offsetof(type,member)是一個常量,list_entry()正是利用這個不變的偏移量來求得鏈表數據項的變量地址。
二、container_of()
container_of() 來自\linux\kernel.h
內核中的注釋:container_of - cast a member of a tructure out to the containing structure。
ptr: the pointer to the member.
type: the type of the container struct this is embedded in.
member:the name of the member within the truct.
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
自己分析:
1.(type *)0->member為設計一個type類型的結構體,起始地址為0,編譯器將結構體的起始的地址加上此結構體成員變量的偏移得到此結構體成員變量的偏移地址,由於結構體起始地址為0,所以此結構體成員變量的偏移地址就等於其成員變量在結構體內的距離結構體開始部分的偏移量。即:&(type *)0->member就是取出其成員變量的偏移地址。而其等於其在結構體內的偏移量:即為:(size_t)(& ((type *)0)->member)經過size_t的強制類型轉換後,其數值為結構體內的偏移量。該偏移量這裡由offsetof()求出。
2.typeof( ( (type *)0)->member )為取出member成員的變量類型。用其定義__mptr指針.ptr為指向該成員變量的指針。__mptr為member數據類型的常量指針,其指向ptr所指向的變量處。
3.(char *)__mptr轉換為字節型指針。(char *)__mptr - offsetof(type,member))用來求出結構體起始地址(為char *型指針),然後(type *)( (char *)__mptr -offsetof(type,member) )在(type *)作用下進行將字節型的結構體起始指針轉換為type *型的結構體起始指針。
4.({ })這個擴展返回程序塊中最後一個表達式的值。
這就是從結構體某成員變量指針來求出該結構體的首指針。指針類型從結構體某成員變量類型轉換為該結構體類型。
三.container_of實現了根據一個結構體變量中的一個域成員變量的指針來獲取指向整個結構體變量的指針的功能。這樣就可以定義自己的數據結構,在此數據結構中用linux的鏈表,再根據此鏈表獲得數據結構的入口地址.