生活随笔
收集整理的這篇文章主要介紹了
[c]如何通过结构体元素找到结构体?
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1. 問題提出 我們知道,如果有一個結構體定義如下:
struct ?_st?{???int ?a;? ??char ?b;? }?st?;? 我們可以通過st訪問到a或者b,方法就是st.a(或者如果有st的指針pst,那么就用pst->a)。但是,如果知道了結構體中元素的指針,是否可以獲得當前結構體的指針呢?或者說,如果我只能訪問到b,我可以訪問到st和a么?
2. 這個問題的實際意義 首先,這樣做有什么用呢?其實,自己早就知道linux內核中linus就是使用了container_of()的宏,就是利用的這個方法。最近,在項目中,碰到了一個問題,想了想暫時沒有想到特別好的方法。而使用這個方法倒是可以比較好的解決問題。
大致上問題抽象出來就是:有一些BLOCK,需要用多個鏈表串起來。比如有B1,B2,B3,B4,B5共5個BLOCK,要用三個不同功能的鏈表串起來:
鏈表1: B1->B4->B5
鏈表2: B2->B5
鏈表3: B1->B2->B3->B4->B5
而項目希望使用glib中的鏈表庫實現。而glib中的鏈表庫(這里舉一個單向鏈表的例子)是這樣組織的(參見Glib文檔):
typedef ?struct ?{???gpointer?data;? ??GSList?*next;? }?GSList; 數據用data指針鏈接起來,實現單向鏈表。這里,如果有人有推薦的庫也希望說出來,呵呵。這里我們如果用glib的庫顯然不容易實現我們想要的功能,因為我們不知道B2的next是要指向B5(如鏈表2中)還是B3(如鏈表3中)。我們顯然需要多個next指針做到這一點。而如果我們這樣使用:
struct ?_block?{???int ?data;? ??GSlist?list1;? ??GSlist?list2;? ??GSlist?list3;? };? 因為每一個GSlist的next指針都是指向下一個block的list1(或者list2/3)元素的,我們也難以得到data字段的數據。這時候,就需要通過list1的指針找到data。而這也就是我們提出的問題。
3. 考慮 大致想一下可以知道,如果我們可以知道結構體在內存中存在的方式。比如:
1. 是否按照定義順序放置在內存中? 2. 先定義的元素和后定義的元素,哪個地址在高段? 3. 是否在元素間有reserved區域用來align? 這樣,我們就可以通過加減法計算出pa所指向的這個×××a所屬于的結構體的地址。而以上的所有,編譯器是肯定知道的,但是我們又怎么在代碼中體現呢?
其實,為了用代碼表示出來,我們根本不用知道上面的問題。看下面的方法一段便知道了。
4. container_of()的分析 ?既然之前知道在linux內核中,linus使用了container_of()這樣tricky的宏,來通過一個結構體中元素的指針獲得當前結構體的指針,這里,直接拿過來用其實就好了。
container_of()的實現方法很簡單,也很巧妙。其核心就是兩個宏定義:
#ifndef?offsetof ?#define?offsetof(type,?field)???((long)?&((type?*)0)->field) ?#endif???/*?offsetof?*/ ?? #ifndef?container_of ?#define?container_of(ptr,?type,?member)?({??????????\ ?????const ?typeof(?((type?*)0)->member?)?*__mptr?=?(ptr);????\? ????(type?*)(?(char ?*)__mptr?-?offsetof(type,member)?);})? #endif ? 這里主要有三個宏:typeof(), offsetof(), container_of()。我談談我的一些理解:
1. typeof()可以得到一個變量的類型,這個是編譯器要支持的。如我們可以這樣寫代碼:{int a; typeof(a) b;}這段代碼等價于:{int a; int b;} 2. offsetof()利用((type *)0)->field方式得到偏移地址,是假設有一個類型為type的結構在內存的0x0000000處,那么這個結構中field的地址的值就是field字段在結構中的偏移地址了! 3. container_of()首先定義了一個type.member同樣類型的指針__mptr,并將ptr賦值給__mptr,這里其實是為了檢驗ptr的類型是否是type.member,增加了安全性(比如,當我隨便傳一個變量的地址ptr給這個宏后,加了校驗的這個container_of()就會在那行報錯,類型不匹配,如果是強制轉換,編譯或許是通過了,但是關鍵時刻一跑估計就是run time error了...);然后用__mptr的地址值減去member在type結構中的偏移地址,得到原始ptr所在結構的結構體地址。 4. 這里還要注意一個括號的使用方法:({...;...;...;}),我沒有對這個的使用在編譯器的手冊中進行查找,但是大致的理解是,在{}中可以按照平時的習慣寫程序,最后({})中可以看作一個值,它的大小是{}中最后一個語句的值。感覺有些類似于(...,...,...)的寫法,但是只有()時不能有運算的語句。這里我寫了一個簡單的例子,輸出是兩個3: int ?main(void )?{? ????printf("%d.\n" ,?(1,2,3));? ????printf("%d.\n" ,?({int ?i=1,j;j=i+2;}));? ????return ?0;? }? 5. container_of()的使用例子 這里我舉一個簡單的使用container_of()的例子:
/* * desc : a simple example to use container_of() macro * mail : xzpeter@gmail.com */ #include?<stdio.h> ?? #ifndef?offsetof ?#define?offsetof(type,?field)???((long)?&((type?*)0)->field) ?#endif???/*?offsetof?*/ ?? #ifndef?container_of ?#define?container_of(ptr,?type,?member)?({??????????\ ?????const ?typeof(?((type?*)0)->member?)?*__mptr?=?(ptr);????\? ????(type?*)(?(char ?*)__mptr?-?offsetof(type,member)?);})? #endif ?? typedef ?struct ?_A?{?????int ?a;? ????char ?b;? ????long ?long ?c;? ????double ?d;? }?SA?;? ? SA?sa?=?{? ????.a?=?10,? ????.b?=?'c' ,? ????.c?=?204,? ????.d?=?3.14,? };? ? int ?main(int ?argc,char ?*argv[])?{? ????double ?*pd?=?&sa.d;? ????? ????printf("SA.c?=?%lld\n" ,?container_of(pd,?SA,?d)->c);? ????return ?0;? }? 這里,我定義了一個結構sa,并通過sa.d的指針得到sa.c的值。輸出是:
SA.c?=?204? 這樣,我們自然也可以通過block.next得到block.data了。
參考資料: 1. ”對linux內核代碼的一點疑惑:container_of的冗余?“, http://hi.baidu.com/joec3/blog/item/37b0c8900e397487a977a493.html,(其實在開始寫這篇文章的時候,沒有弄清楚為何要專門要用__mptr這個const指針,在這里找到的答案。同時,還有一篇和我這里分析container_of()很像的文章,都是從三個宏的角度。放到參考2里吧,呵呵) 2. “對linux內核中container_of宏的理解“, http://hi.baidu.com/tim_bi/blog/item/fdc3d81358e1f60b5aaf53c6.html
轉載于:https://blog.51cto.com/xzpeter/339497
總結
以上是生活随笔 為你收集整理的[c]如何通过结构体元素找到结构体? 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。