在學習數據結構課時,咱們知道鏈表元素是個結構體,由數據項和指針項構成,正式裏面的指針項是造成鏈表結構的核心,但數據項纔是鏈表有意義的依託,若是一個鏈表元素只有指針項,沒有數據項,這個鏈表是沒有意義的。但這只是表面如此。在學習linux內核的雙向循環鏈表中,咱們不得不歎服內核設計者的匠心獨具。List_head 結構定義在include/linux/types.h中 linux
struct list_head { ios
struct list_head *next, *prev; 數據結構
}; 學習
這個結構體沒有數據項!也就是說,n多個這種元素能夠首尾相連造成一個雙向循環的環,可是在這個鏈表中沒有信息的載體。那麼一切鏈表的操做,好比遍歷,在這裏有什麼意義呢?總不能就在一堆指針裏閒逛吧。 測試
咱們要讓鏈表有意義,必須使鏈表有信息的載體,即數據項,這是毋庸置疑的。那就讓咱們換個思路,既然一堆list_head已經構成一個鏈表,並且咱們不能往list_head對象裏面加入數據項,那麼讓這些list_head對象位於另一個結構體的內部,做爲指針域存在,這個外部結構體的其餘成員做爲數據項存在,是否就讓鏈表有意義了呢。 this
答案是確定的,可是問題又來了,N多外部結構體的對象經過包含list_head相互聯繫起來,但是在對鏈表進行操做時,咱們的目的是要操做或訪問與之相關的數據。而如今數據是存在於List_head對象外的,咱們不能經過鏈表元素的指針來訪問。事實上,咱們只能經過同時包含這些數據項和鏈表元素的外部結構體對象的指針來訪問,爲何呢?哈哈,繞暈了,由於這些數據原本就是這些外部結構體對象的內容之一啊。 spa
如今問題歸結爲:已知一個結構體裏某個成員的指針,怎麼獲得這個結構體的指針? .net
試想,若是知道了這個已知成員在結構體裏的地址偏移,咱們就能獲得這個結構體的指針。如是List_head類型的鏈表的操做才顯得有意義。 設計
到如今全部的問題都指向了一個宏,container_of(),這個宏纔是整個鏈表結構的最本質的地方。該宏定義在kernel.h中。 指針
/**
* container_of - cast a member of a structure 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 struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
其中offsetof的定義爲:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
offsetof宏的做用是返回一個TYPE類型結構的MEMBER成員在該結構中的編譯。把零地址強制轉換一個指向該結構體對象的指針,那麼該對象中MEMBER成員的地址就是該成員在這個結構體中的偏移。
那麼container_of的工做原理就至關簡單了,就是用MEMBER的地址減去上一步中獲得的偏移,即爲TYPE類型的結構體對象的地址。
爲何__mptr要強制轉換爲char*呢?這是c語言的基礎知識,指針的加減對應到地址是根據指針所指的數的類型來肯定的。若是MEMBER是int,而不把它的指針強制爲char*,那麼減去的將是4倍的地址偏移,固然是不對的。
既然內核中鏈表如此精妙,咱們是否能夠將其納爲己用呢,固然能夠,只要咱們實現了那個container_of宏就行。事實上,這是至關簡單的,只需一個定義:
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )就能夠了。Vc裏預約義了offsetof宏,其語義跟linux內核相同。
在VC上的測試:
#define container_of(ptr, type, member) (type *)( (char *)ptr - offsetof(type,member) )
#include<iostream>
using namespace std;
int main()
{
struct StructA
{
int a1;
int a2;
};
struct StructB
{
int b1;
int b2;
StructA a;
};
struct StructC
{
int c1;
int c2;
StructB b;
};
StructA aa;
aa.a1=11;
aa.a2=22;
StructB bb;
bb.b1=33;
bb.b2=44;
bb.a=aa;
StructC cc;
cc.c1=55;
cc.c2=66;
cc.b=bb;
StructA *pa=container_of(&aa.a1,StructA,a1);
cout<<pa->a1<<" "<<pa->a2<<endl;
StructB *pb=container_of(&bb.b2,StructB,b2);
cout<<pb->b1<<" "<<pb->b2<<" "<<(pb->a).a1<<" "<<(pb->a).a2<<endl;
StructB *pb2=container_of(pa,StructB,a);
cout<<pb->b1<<" "<<pb->b2<<" "<<(pb->a).a1<<" "<<(pb->a).a2<<endl;
StructC *pc=container_of(&cc.c1,StructC,c1);
cout<<pc->c1<<" "<<pc->c2<<" "<<(pc->b).b1<<" "<<(pc->b).b2<<" "<<(pc->b).a.a1<<" "<<(pc->b).a.a2<<endl;
StructC *pc2=container_of(pb,StructC,b);
cout<<pc->c1<<" "<<pc->c2<<" "<<(pc->b).b1<<" "<<(pc->b).b2<<" "<<(pc->b).a.a1<<" "<<(pc->b).a.a2<<endl;
}
運行結果爲:
至關溫馨啊。今後,咱們能夠把List_head和list.h中全部對鏈表的操做當作本身的東西了。