前言
相信大家在c語言程式開發的過程一定都使用過結構體,那麼不知你對結構體中成員變數位移這塊是如何理解的。本文將和大家一起分享下,本人最近關於c語言中結構體位移的一些思考和總結。
另外這篇博文還可以幫你更好的理解這個問題c語言中兩種宏定義的區別,關於這個思考有哪些方面的意義,細心的你可能發現本文所屬的類別為linux核心設計與實現,而並非 GNU C語言編程,可能有些同學會有些許好奇。不過不用著急,如果對本篇博文意義感興趣的同學,可繼續關注後續的博文,會有進一步的闡述。 樣本1
我們先來定義一下需求:
已知結構體類型定義如下:
struct node_t{ char a; int b; int c;};
且結構體1Byte對齊
#pragma pack(1)
求:
結構體struct node_t中成員變數c的位移。
註:這裡的位移量指的是相對於結構體起始位置的位移量。
看到這個問題的時候,我相信不同的人腦中浮現的解決方案可能會有所差異,下面我們分析以下幾種可能的解法: 方法1
如果你對c語言的庫函數比較熟悉的話,那麼你第一個想到的肯定是offsetof函數(其實只是個宏而已,先姑且這樣叫著吧),我們man 3 offsetof查看函數原型如下:
#include <stddef.h> size_t offsetof(type, member);
有了上述的庫函數,我們用一行代碼就可以搞定:
offsetof(struct node_t, c);
當然這並非本文探討的重點,請繼續閱讀。
方法2
當我們對c語言的庫函數不熟悉的時候,此時也不要著急,我們依然可以使用我們自己的方法來解決問題。
最直接的思路是:【結構體成員變數c的地址】 減去 【結構體起始地址】
我們先來定義一個結構體變數node:
struct node_t node;
接著來導出成員變數c的位移量:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
&(node.c)為結構體成員變數c的地址,並強制轉化為unsigned long;
&node為結構體的起始地址,也強制轉化為unsigned long;
最後我們將上述兩值相減,得到成員變數c的位移量; 方法3
按照方法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) 樣本2
和樣本1一樣,我們先定義需求如下:
已知結構體類型定義如下:
struct node_t{ char a; int b; int c;};
int *p_c,該指標指向struct node_t x的成員變數c
結構體1Byte對齊
#pragma pack(1)
求:
結構體x的成員變數b的值。
拿到這個問題的時候,我們先做一下簡單的分析,題目的意思是根據一個指向某結構體成員變數的指標,如何求該結構體的另外一個成員變數的值。
那麼可能的幾種解法有: 方法1
由於我們知道結構體是1Byte對齊的,所以這道題最簡單的解法是:
*(int *)((unsigned long)p_c - sizeof(int))
上述代碼很簡單,成員變數c的地址減去sizeof(int)從而得到成員變數b的地址,然後再強制轉換為int *,最後再取值最終得到成員變數b的值; 方法2
方法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))