相信大家在c語言程式開發的過程一定都使用過結構體,那麼不知你對結構體中成員變數偏移這塊是如何理解的?本文將和大家一起分享下,本人最近關於c語言中結構體偏移的一些思考和總結。
另外這篇博文還可以幫你更好的理解這個問題c語言中兩種巨集定義的區別,關於這個思考有哪些方面的意義,細心的你可能發現本文所屬的類別為linux核心設計與實現,而並非
gnu c語言程式設計
,可能有些同學會有些許好奇
。不過不用著急,如果
對本篇博文意義感興趣的同學,
可繼續關注後續的博文,會有進一步的闡述。
我們先來定義一下需求:
已知結構體型別定義如下:
struct node_t;
且結構體1byte對齊
#pragma pack(1)
求:結構體struct node_t中成員變數c的偏移。
注:這裡的偏移量指的是相對於結構體起始位置的偏移量。
看到這個問題的時候,我相信不同的人腦中浮現的解決方法可能會有所差異,下面我們分析以下幾種可能的解法:
如果你對c語言的庫函式比較熟悉的話,那麼你第乙個想到的肯定是offsetof函式(其實只是個巨集而已,先姑且這樣叫著吧),我們man 3 offsetof檢視函式原型如下:
#include size_t offsetof(type, member);
有了上述的庫函式,我們用一行**就可以搞定:
offsetof(struct node_t, c);
當我們對c語言的庫函式不熟悉的時候,此時也不要著急,我們依然可以使用我們自己的方法來解決問題。
最直接的思路是:【
結構體成員變數c的位址】 減去 【結構體起始位址】
我們先來定義乙個結構體變數node:
struct node_t node;
接著來計算成員變數c的偏移量:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
&(node.c)為結構體成員變數c的位址,並強制轉化為unsigned long;
&node為結構體的起始位址,也強制轉化為unsigned long;
最後我們將上述兩值相減,得到成員變數c的偏移量;
按照方法2的思路我們在不借助庫函式的情況下,依然可以得到成員變數c的偏移量。但作為程式設計師,我們應該善於思考,是不是可以針對上面的**做一些改進,使我們的**變得更簡潔一些?在做具體的改進之前,我們應該分析方法2存在哪些方面的問題。
相信不用我多說,細心的你一定已經察覺到,方法2中最主要的乙個問題是我們自定義了乙個結構體變數node,雖然題目中並未限制我們可以自定義變數,但當我們遇到比較嚴且題目中不允許自定義變數的時候,此時我們就要思考新的解決方法。
在**新的解決方法之前,我們先來**乙個有關偏移的小問題:
小問題這是一道簡單的幾何問題,假設在座標軸上由a點移動到b點,如何計算b相對於a的偏移?這個問題對於我們來說是非常的簡單,可能大部分人都會脫口而出並得到答案為b-a。
那麼這個答案是否完全準確呢?比較嚴謹的你覺得顯然不是,原因在於,當a為座標原點即a=0的時候,上述答案b-a就直接簡化為b了。
這個小小的簡單的問題,對於我們來說有什麼啟示呢?
我們結合方法2的思路和上述的小問題,是不是很快就得到了下面的關聯:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
和b - a
我們小問題的思路是當a為座標原點的時候,b-a就簡化為b了,那麼對應到我們的方法2,當node的記憶體位址為0即(&node==0)的時候,上面的**可簡化為:
(unsigned long)(&(node.c))
由於node記憶體位址==0了,所以
node.c //結構體node中成員變數c
我們就可以使用另外一種方式來表達了,如下:
((struct node_t *)0)->c
上述**應該比較好理解,由於我們知道結構體的記憶體位址編號為0,所以我們就可以直接通過記憶體位址的方式來訪問該結構體的成員變數,相應的**的含義就是 獲取記憶體位址編號為0的結構體struct node_t的成員變數c。
注:此處只是利用了編譯器的特性來計算結構體偏移,並未對記憶體位址0有任何操作,有些同學對此可能還有些疑問,詳細的了解該問題可參考
關於c語言結構體成員變數訪問方式的一點思考。
此時,我們的偏移求法就消除了struct node_t node這個自定義變數,直接一行**解決,:
(unsigned long)(&(((struct node_t *)0)->c))
上述的**相對於方法2是不是更簡潔了一些。
這裡我們將上面的**功能定義為乙個巨集,該巨集的作用是用來計算某結構體內成員變數的偏移(後面的示例會使用該巨集):
#define offset_of(type, member) (unsigned long)(&(((type *)0)->member))
使用上面的巨集,就可以直接得到成員變數c在結構體struct node_t中的偏移為:
offset_of(struct node_t, c)
和示例1一樣,我們先定義需求如下:
已知結構體型別定義如下:
struct node_t;
int *p_c,該指標指向struct node_t x的成員變數c
結構體1byte對齊
#pragma pack(1)
求:結構體x的成員變數b的值?
拿到這個問題的時候,我們先做一下簡單的分析,題目的意思是根據乙個指向某結構體成員變數的指標,如何求該結構體的另外乙個成員變數的值。
那麼可能的幾種解法有:
由於我們知道結構體是1byte對齊的,所以這道題最簡單的解法是:
*(int *)((unsigned long)p_c - sizeof(int))
上述**很簡單,成員變數c的位址減去sizeof(int)從而得到成員變數b的位址,然後再強制轉換為int *,最後再取值最終得到成員變數b的值;
方法1的**雖然簡單,但擴充套件性不夠好。我們希望通過p_c直接得到指向該結構體的指標p_node,然後通過p_node訪問該結構體的任意成員變數了。
由此我們得到計算結構體起始位址p_node的思路為:
【成員變數c的位址p_c】減去【c在結構體中的偏移】
由示例1,我們得到結構體struct node_t中成員變數c的偏移為:
(unsigned long)&(((struct node_t *)0)->c)
所以我們得到結構體的起始位址指標p_node為:
(struct node_t *)((unsigned long)p_c - (unsigned long)(&((struct node_t *)0)->c))
我們也可以直接使用示例1中定義的offset_of巨集,則上面的**變為:
(struct node_t *)((unsigned long)p_c - offset_of(struct node_t, c))
最後我們就可以使用下面的**來獲取成員變數a,b的值:
p_node->a
p_node->b
我們同樣將上述**的功能定義為如下巨集:
#define struct_entry(ptr, type, member) (type *)((unsigned long)(ptr)-offset_of(type, member))
該巨集的功能是通過結構體任意成員變數的指標來獲得指向該結構體的指標。
我們使用上面的巨集來修改之前的**如下:
struct_entry(p_c, struct node_t, c)
注:
上述示例中關於位址運算的一些說明:
int a = 10;
int * p_a = &a;
設p_a == 0x95734104;
以下為編譯器計算的相關結果:
p_a + 10 == p_a + sizeof(int)*10 =
0x95734104 + 4*10 = 0x95734144
(unsigned long)p_a + 10 ==
0x95734104+10 = 0x95734114
(char *)p_a + 10 ==
0x95734104 + sizeof(char)*10 = 0x95734114
從上述三種情況,相信你應該能體會到我所要表達的意思了。(注:後續某博文將從編譯器的角度對該問題進行詳細的闡述)
本文通過幾個示例描述了c語言結構體有關偏移的一些有意思的事情,希望能夠對你有所幫助。為什麼會有上述思考,相信有些同學已經看出一些端倪,這也正是後續博文將要描述的主題。
如文中有錯誤之處,歡迎指出。
[1]
[2]
[3]
c語言結構體的偏移 c
c語言中關於結構體的位置偏移原則簡單,但經常忘記,做點筆記以是個記憶的好辦法 原則有三個 a.結構體中的所有成員其首位址偏移量必須為器資料型別長度的整數被,其中第乙個成員的首位址偏移量為0,例如,若第二個成員型別為int,則其首位址偏移量必須為4的倍數,否則就要 首部填充 以此類推 b.結構體所佔的...
關於C語言的隨機函式的一點思考
今天在看書的過程遇到乙個問題,大致描述如下 編寫乙個函式,通過返回範圍1至6的隨機整數來模擬擲篩子。同時,這個題目要求這個6個數字出現的概率是相等的。首先,對於這個問題的基本思路如下 首先,用srand函式對隨機數發生器進行初始化。其次,迴圈呼叫rand函式六次,對每一次呼叫的結果與6取模,然後再加...
關於結構體的一點小知識
用結構體建立鍊錶 struct student 其中成員num和score用來存放結點中的有用資料 使用者需要用到的資料 next是指標型別的成員,它指向struct student型別資料 這就是next所在的結構體型別 使幾個不同的變數共占同一段記憶體的結構 稱為 共用體 型別的結構。定義共用體...