如何通過結構體元素找到結構體?

2021-06-16 09:37:50 字數 2929 閱讀 7569

我們知道,如果有乙個結構體定義如下:

struct

_st  st ; 

我們可以通過st訪問到a或者b,方法就是st.a(或者如果有st的指標pst,那麼就用pst->a)。但是,如果知道了結構體中元素的指標,是否可以獲得當前結構體的指標呢?或者說,如果我只能訪問到b,我可以訪問到st和a麼?

首先,這樣做有什麼用呢?其實,自己早就知道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

gslist;

資料用data指標鏈結起來,實現單向鍊錶。這裡,如果有人有推薦的庫也希望說出來,呵呵。這裡我們如果用glib的庫顯然不容易實現我們想要的功能,因為我們不知道b2的next是要指向b5(如鍊表2中)還是b3(如鍊表3中)。我們顯然需要多個next指標做到這一點。而如果我們這樣使用:

struct

_block ; 

因為每乙個gslist的next指標都是指向下乙個block的list1(或者list2/3)元素的,我們也難以得到data欄位的資料。這時候,就需要通過list1的指標找到data。而這也就是我們提出的問題。

大致想一下可以知道,如果我們可以知道結構體在記憶體中存在的方式。比如:

1. 是否按照定義順序放置在記憶體中?

2. 先定義的元素和後定義的元素,哪個位址在高段?

3. 是否在元素間有reserved區域用來align?

這樣,我們就可以通過加減法計算出pa所指向的這個整形a所屬於的結構體的位址。而以上的所有,編譯器是肯定知道的,但是我們又怎麼在**中體現呢?

其實,為了用**表示出來,我們根本不用知道上面的問題。看下面的方法一段便知道了。

既然之前知道在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) () 

#endif

這裡主要有三個巨集:typeof(), offsetof(), container_of()。我談談我的一些理解:

1. typeof()可以得到乙個變數的型別,這個是編譯器要支援的。如我們可以這樣寫**:這段**等價於:

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

) )); 

return

0; } 

這裡我舉乙個簡單的使用container_of()的例子:

/*

* desc : a ****** example to use container_of() macro

* mail : [email protected]

*/#include 

#ifndef offsetof

#define offsetof(type, field)   ((long) &((type *)0)->field)

#endif   /* offsetof */

#ifndef container_of

#define container_of(ptr, type, member) () 

#endif

typedef

struct

_a  sa ; 

sa sa = ; 

intmain(

intargc,

char

*argv) 

這裡,我定義了乙個結構sa,並通過sa.d的指標得到sa.c的值。輸出是:

sa.c = 204 

這樣,我們自然也可以通過block.next得到block.data了。

結構體型別 結構體變數 結構體陣列 結構體指標

問題1 一元錢換為1 2 5分的硬幣,有多少種兌換方?本題要點分析及參 對各種可能情況進行一一測試。這是實現迴圈的一種方式 窮舉法 但實際上只有只有餘額才能兌換成其它面值的硬幣 main 注意換行的控制和每列的對齊 問題3 某鐵路線上有10個站,需要準備多少種客票?main b a 0 p prin...

結構體 結構體陣列

void test 函式遞迴呼叫 允許函式體裡再次呼叫函式本身 使用遞迴一定要有出口 long fact int n return n fact n 1 定義乙個新的資料型別 struct mypoint 結構體型別所占用的記憶體空間是最大資料型別的整數倍。因為結構體型別的變數在分配記憶體時有記憶體...

結構體 結構體陣列

struct 結構體型別名 型別名 成員名 型別名 成員名 先宣告結構體型別,再定義結構體變數名 宣告結構體型別,不分配空間 定義結構體型別變數,就要分配記憶體空間 作 者 hh 完成日期 2018年8月15日 版本號 v1.0 問題描述 結構體巢狀 賦值 輸出 輸入描述 程式輸出 include ...