詳解C語(yǔ)言的結(jié)構(gòu)體中成員變量偏移問(wèn)題
c語(yǔ)言中關(guān)于結(jié)構(gòu)體的位置偏移原則簡(jiǎn)單,但經(jīng)常忘記,做點(diǎn)筆記以是個(gè)記憶的好辦法
原則有三個(gè):
a.結(jié)構(gòu)體中的所有成員其首地址偏移量必須為器數(shù)據(jù)類型長(zhǎng)度的整數(shù)被,其中第一個(gè)成員的首地址偏移量為0,
例如,若第二個(gè)成員類型為int,則其首地址偏移量必須為4的倍數(shù),否則就要“首部填充”;以此類推
b.結(jié)構(gòu)體所占的總字節(jié)數(shù)即sizeof()函數(shù)返回的值必須是最大成員的長(zhǎng)度的整數(shù)倍,否則要進(jìn)行“末尾填充”;
c.若結(jié)構(gòu)體A將結(jié)構(gòu)體B作為其成員,則結(jié)構(gòu)體B存儲(chǔ)的首地址的偏移量必須為B中所含成員數(shù)據(jù)長(zhǎng)度最大值的整數(shù)倍,
如若B中成員為int,double,char,則B的偏移量要為8的整數(shù)倍;否則進(jìn)行“中間填充”。
相信大家在c語(yǔ)言程序開(kāi)發(fā)的過(guò)程一定都使用過(guò)結(jié)構(gòu)體,那么不知你對(duì)結(jié)構(gòu)體中成員變量偏移這塊是如何理解的?本文將和大家一起分享下,本人最近關(guān)于c語(yǔ)言中結(jié)構(gòu)體偏移的一些思考和總結(jié)。
示例1
我們先來(lái)定義一下需求:
已知結(jié)構(gòu)體類型定義如下:
struct node_t{ char a; int b; int c; };
且結(jié)構(gòu)體1Byte對(duì)齊
#pragma pack(1)
求:
結(jié)構(gòu)體struct node_t中成員變量c的偏移。
注:這里的偏移量指的是相對(duì)于結(jié)構(gòu)體起始位置的偏移量。
看到這個(gè)問(wèn)題的時(shí)候,我相信不同的人腦中浮現(xiàn)的解決方法可能會(huì)有所差異,下面我們分析以下幾種可能的解法:
方法1
如果你對(duì)c語(yǔ)言的庫(kù)函數(shù)比較熟悉的話,那么你第一個(gè)想到的肯定是offsetof函數(shù)(其實(shí)只是個(gè)宏而已,先姑且這樣叫著吧),我們man 3 offsetof查看函數(shù)原型如下:
#include <stddef.h> size_t offsetof(type, member);
有了上述的庫(kù)函數(shù),我們用一行代碼就可以搞定:
offsetof(struct node_t, c);
當(dāng)然這并非本文探討的重點(diǎn),請(qǐng)繼續(xù)閱讀。
方法2
當(dāng)我們對(duì)c語(yǔ)言的庫(kù)函數(shù)不熟悉的時(shí)候,此時(shí)也不要著急,我們依然可以使用我們自己的方法來(lái)解決問(wèn)題。
最直接的思路是:【結(jié)構(gòu)體成員變量c的地址】 減去 【結(jié)構(gòu)體起始地址】
我們先來(lái)定義一個(gè)結(jié)構(gòu)體變量node:
struct node_t node;
接著來(lái)計(jì)算成員變量c的偏移量:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
&node為結(jié)構(gòu)體的起始地址,也強(qiáng)制轉(zhuǎn)化為unsigned long;
最后我們將上述兩值相減,得到成員變量c的偏移量;
方法3
按照方法2的思路我們?cè)诓唤柚鷰?kù)函數(shù)的情況下,依然可以得到成員變量c的偏移量。但作為程序員,我們應(yīng)該善于思考,是不是可以針對(duì)上面的代碼做一些改進(jìn),使我們的代碼變得更簡(jiǎn)潔一些?在做具體的改進(jìn)之前,我們應(yīng)該分析方法2存在哪些方面的問(wèn)題。
相信不用我多說(shuō),細(xì)心的你一定已經(jīng)察覺(jué)到,方法2中最主要的一個(gè)問(wèn)題是我們自定義了一個(gè)結(jié)構(gòu)體變量node,雖然題目中并未限制我們可以自定義變量,但當(dāng)我們遇到比較嚴(yán)且題目中不允許自定義變量的時(shí)候,此時(shí)我們就要思考新的解決方法。
在探討新的解決方法之前,我們先來(lái)探討一個(gè)有關(guān)偏移的小問(wèn)題:
小問(wèn)題
這是一道簡(jiǎn)單的幾何問(wèn)題,假設(shè)在座標(biāo)軸上由A點(diǎn)移動(dòng)到B點(diǎn),如何計(jì)算B相對(duì)于A的偏移?這個(gè)問(wèn)題對(duì)于我們來(lái)說(shuō)是非常的簡(jiǎn)單,可能大部分人都會(huì)脫口而出并得到答案為B-A。
那么這個(gè)答案是否完全準(zhǔn)確呢?比較嚴(yán)謹(jǐn)?shù)哪阌X(jué)得顯然不是,原因在于,當(dāng)A為坐標(biāo)原點(diǎn)即A=0的時(shí)候,上述答案B-A就直接簡(jiǎn)化為B了。
這個(gè)小小的簡(jiǎn)單的問(wèn)題,對(duì)于我們來(lái)說(shuō)有什么啟示呢?
我們結(jié)合方法2的思路和上述的小問(wèn)題,是不是很快就得到了下面的關(guān)聯(lián):
(unsigned long)(&(node.c)) - (unsigned long)(&node)
和
B - A
我們小問(wèn)題的思路是當(dāng)A為坐標(biāo)原點(diǎn)的時(shí)候,B-A就簡(jiǎn)化為B了,那么對(duì)應(yīng)到我們的方法2,當(dāng)node的內(nèi)存地址為0即(&node==0)的時(shí)候,上面的代碼可簡(jiǎn)化為:
(unsigned long)(&(node.c))
由于node內(nèi)存地址==0了,所以
node.c //結(jié)構(gòu)體node中成員變量c
我們就可以使用另外一種方式來(lái)表達(dá)了,如下:
((struct node_t *)0)->c
上述代碼應(yīng)該比較好理解,由于我們知道結(jié)構(gòu)體的內(nèi)存地址編號(hào)為0,所以我們就可以直接通過(guò)內(nèi)存地址的方式來(lái)訪問(wèn)該結(jié)構(gòu)體的成員變量,相應(yīng)的代碼的含義就是 獲取內(nèi)存地址編號(hào)為0的結(jié)構(gòu)體struct node_t的成員變量c。
注:此處只是利用了編譯器的特性來(lái)計(jì)算結(jié)構(gòu)體偏移,并未對(duì)內(nèi)存地址0有任何操作,有些同學(xué)對(duì)此可能還有些疑問(wèn),詳細(xì)的了解該問(wèn)題可參考關(guān)于c語(yǔ)言結(jié)構(gòu)體成員變量訪問(wèn)方式的一點(diǎn)思考。
此時(shí),我們的偏移求法就消除了struct node_t node這個(gè)自定義變量,直接一行代碼解決,:
(unsigned long)(&(((struct node_t *)0)->c))
上述的代碼相對(duì)于方法2是不是更簡(jiǎn)潔了一些。
這里我們將上面的代碼功能定義為一個(gè)宏,該宏的作用是用來(lái)計(jì)算某結(jié)構(gòu)體內(nèi)成員變量的偏移(后面的示例會(huì)使用該宏):
#define OFFSET_OF(type, member) (unsigned long)(&(((type *)0)->member))
使用上面的宏,就可以直接得到成員變量c在結(jié)構(gòu)體struct node_t中的偏移為:
OFFSET_OF(struct node_t, c)
示例2
和示例1一樣,我們先定義需求如下:
已知結(jié)構(gòu)體類型定義如下:
struct node_t{ char a; int b; int c; };
int *p_c,該指針指向struct node_t x的成員變量c
結(jié)構(gòu)體1Byte對(duì)齊
#pragma pack(1)
求:
結(jié)構(gòu)體x的成員變量b的值?
拿到這個(gè)問(wèn)題的時(shí)候,我們先做一下簡(jiǎn)單的分析,題目的意思是根據(jù)一個(gè)指向某結(jié)構(gòu)體成員變量的指針,如何求該結(jié)構(gòu)體的另外一個(gè)成員變量的值。
那么可能的幾種解法有:
方法1
由于我們知道結(jié)構(gòu)體是1Byte對(duì)齊的,所以這道題最簡(jiǎn)單的解法是:
*(int *)((unsigned long)p_c - sizeof(int))
上述代碼很簡(jiǎn)單,成員變量c的地址減去sizeof(int)從而得到成員變量b的地址,然后再?gòu)?qiáng)制轉(zhuǎn)換為int *,最后再取值最終得到成員變量b的值;
方法2
方法1的代碼雖然簡(jiǎn)單,但擴(kuò)展性不夠好。我們希望通過(guò)p_c直接得到指向該結(jié)構(gòu)體的指針p_node,然后通過(guò)p_node訪問(wèn)該結(jié)構(gòu)體的任意成員變量了。
由此我們得到計(jì)算結(jié)構(gòu)體起始地址p_node的思路為:
【成員變量c的地址p_c】減去【c在結(jié)構(gòu)體中的偏移】
由示例1,我們得到結(jié)構(gòu)體struct node_t中成員變量c的偏移為:
(unsigned long)&(((struct node_t *)0)->c)
所以我們得到結(jié)構(gòu)體的起始地址指針p_node為:
(struct node_t *)((unsigned long)p_c - (unsigned long)(&((struct node_t *)0)->c))
我們也可以直接使用示例1中定義的OFFSET_OF宏,則上面的代碼變?yōu)椋?/p>
(struct node_t *)((unsigned long)p_c - OFFSET_OF(struct node_t, c))
最后我們就可以使用下面的代碼來(lái)獲取成員變量a,b的值:
p_node->a p_node->b
我們同樣將上述代碼的功能定義為如下宏:
我們使用上面的宏來(lái)修改之前的代碼如下:
STRUCT_ENTRY(p_c, struct node_t, c)
p_c為指向結(jié)構(gòu)體struct node_t成員變量c的指針;
struct node_t結(jié)構(gòu)體類型;
c為p_c指向的成員變量;
注:
上述示例中關(guān)于地址運(yùn)算的一些說(shuō)明:
int a = 10; int * p_a = &a;
設(shè)
p_a == 0x95734104;
以下為編譯器計(jì)算的相關(guān)結(jié)果:
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
從上述三種情況,相信你應(yīng)該能體會(huì)到我所要表達(dá)的意思了。(注:后續(xù)某博文將從編譯器的角度對(duì)該問(wèn)題進(jìn)行詳細(xì)的闡述)
結(jié)論
本文通過(guò)幾個(gè)示例描述了c語(yǔ)言結(jié)構(gòu)體有關(guān)偏移的一些有意思的事情,希望能夠?qū)δ阌兴鶐椭?。為什么?huì)有上述思考,相信有些同學(xué)已經(jīng)看出一些端倪,這也正是后續(xù)博文將要描述的主題。
如文中有錯(cuò)誤之處,歡迎指出。
上一篇:簡(jiǎn)明的C++函數(shù)指針學(xué)習(xí)教程
欄 目:C語(yǔ)言
本文標(biāo)題:詳解C語(yǔ)言的結(jié)構(gòu)體中成員變量偏移問(wèn)題
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2365.html
您可能感興趣的文章
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用函數(shù)刪除字符
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)式函數(shù)庫(kù)
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)數(shù)怎么表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段函數(shù)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排序法函數(shù)
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段函數(shù)
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎么打出三角函數(shù)的值
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求階乘


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 01-10C#中split用法實(shí)例總結(jié)
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載