C語言中結(jié)構(gòu)體偏移及結(jié)構(gòu)體成員變量訪問方式的問題討論
c語言結(jié)構(gòu)體偏移
示例1
我們先來定義一下需求:
已知結(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è)問題的時(shí)候,我相信不同的人腦中浮現(xiàn)的解決方法可能會(huì)有所差異,下面我們分析以下幾種可能的解法:
方法1
如果你對(duì)c語言的庫函數(shù)比較熟悉的話,那么你第一個(gè)想到的肯定是offsetof函數(shù)(其實(shí)只是個(gè)宏而已,先姑且這樣叫著吧),我們man 3 offsetof查看函數(shù)原型如下:
#include <stddef.h> size_t offsetof(type, member);
有了上述的庫函數(shù),我們用一行代碼就可以搞定:
offsetof(struct node_t, c);
當(dāng)然這并非本文探討的重點(diǎn),請(qǐng)繼續(xù)閱讀。
方法2
當(dāng)我們對(duì)c語言的庫函數(shù)不熟悉的時(shí)候,此時(shí)也不要著急,我們依然可以使用我們自己的方法來解決問題。
最直接的思路是:【結(jié)構(gòu)體成員變量c的地址】 減去 【結(jié)構(gòu)體起始地址】
我們先來定義一個(gè)結(jié)構(gòu)體變量node:
struct node_t node;
接著來計(jì)算成員變量c的偏移量:
(unsigned long)(&(node.c)) - (unsigned long)(&node)
&(node.c)為結(jié)構(gòu)體成員變量c的地址,并強(qiáng)制轉(zhuǎn)化為unsigned long;
&node為結(jié)構(gòu)體的起始地址,也強(qiáng)制轉(zhuǎn)化為unsigned long;
最后我們將上述兩值相減,得到成員變量c的偏移量;
方法3
按照方法2的思路我們?cè)诓唤柚鷰旌瘮?shù)的情況下,依然可以得到成員變量c的偏移量。但作為程序員,我們應(yīng)該善于思考,是不是可以針對(duì)上面的代碼做一些改進(jìn),使我們的代碼變得更簡(jiǎn)潔一些?在做具體的改進(jìn)之前,我們應(yīng)該分析方法2存在哪些方面的問題。
相信不用我多說,細(xì)心的你一定已經(jīng)察覺到,方法2中最主要的一個(gè)問題是我們自定義了一個(gè)結(jié)構(gòu)體變量node,雖然題目中并未限制我們可以自定義變量,但當(dāng)我們遇到比較嚴(yán)且題目中不允許自定義變量的時(shí)候,此時(shí)我們就要思考新的解決方法。
在探討新的解決方法之前,我們先來探討一個(gè)有關(guān)偏移的小問題:
小問題
這是一道簡(jiǎn)單的幾何問題,假設(shè)在座標(biāo)軸上由A點(diǎn)移動(dòng)到B點(diǎn),如何計(jì)算B相對(duì)于A的偏移?這個(gè)問題對(duì)于我們來說是非常的簡(jiǎn)單,可能大部分人都會(huì)脫口而出并得到答案為B-A。
那么這個(gè)答案是否完全準(zhǔn)確呢?比較嚴(yán)謹(jǐn)?shù)哪阌X得顯然不是,原因在于,當(dāng)A為坐標(biāo)原點(diǎn)即A=0的時(shí)候,上述答案B-A就直接簡(jiǎn)化為B了。
這個(gè)小小的簡(jiǎn)單的問題,對(duì)于我們來說有什么啟示呢?
我們結(jié)合方法2的思路和上述的小問題,是不是很快就得到了下面的關(guān)聯(lián):
(unsigned long)(&(node.c)) - (unsigned long)(&node)
和
B - A
我們小問題的思路是當(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
((struct node_t *)0)->c
上述代碼應(yīng)該比較好理解,由于我們知道結(jié)構(gòu)體的內(nèi)存地址編號(hào)為0,所以我們就可以直接通過內(nèi)存地址的方式來訪問該結(jié)構(gòu)體的成員變量,相應(yīng)的代碼的含義就是 獲取內(nèi)存地址編號(hào)為0的結(jié)構(gòu)體struct node_t的成員變量c。
此時(shí),我們的偏移求法就消除了struct node_t node這個(gè)自定義變量,直接一行代碼解決,:
(unsigned long)(&(((struct node_t *)0)->c))
上述的代碼相對(duì)于方法2是不是更簡(jiǎn)潔了一些。
這里我們將上面的代碼功能定義為一個(gè)宏,該宏的作用是用來計(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è)問題的時(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的地址,然后再強(qiáng)制轉(zhuǎn)換為int *,最后再取值最終得到成員變量b的值;
方法2
方法1的代碼雖然簡(jiǎn)單,但擴(kuò)展性不夠好。我們希望通過p_c直接得到指向該結(jié)構(gòu)體的指針p_node,然后通過p_node訪問該結(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))
最后我們就可以使用下面的代碼來獲取成員變量a,b的值:
p_node->a
p_node->b
我們同樣將上述代碼的功能定義為如下宏:
#define STRUCT_ENTRY(ptr, type, member) (type *)((unsigned long)(ptr)-OFFSET_OF(type, member))
該宏的功能是通過結(jié)構(gòu)體任意成員變量的指針來獲得指向該結(jié)構(gòu)體的指針。
我們使用上面的宏來修改之前的代碼如下:
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)算的一些說明:
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á)的意思了。
結(jié)構(gòu)體成員變量訪問方式
訪問結(jié)構(gòu)體成員變量?如此簡(jiǎn)單的問題,有什么可以思考的呢?很納悶也很奇怪。既然這樣,那就帶著這個(gè)奇怪的問題繼續(xù)閱讀吧。
示例3
我們的探討還是從一個(gè)簡(jiǎn)單的示例開始:
已知結(jié)構(gòu)體類型定義如下:
struct node_t {
char a;
int b;
int c;
};
且結(jié)構(gòu)體1Byte對(duì)齊:
#pragma pack(1)
接下來我們探討幾種訪問該結(jié)構(gòu)體成員變量c的方式:
情形1
如果程序中定義了一個(gè)struct node_t類型的變量node如下:
struct node_t node;
那么我們就可以直接通過下面的方式來訪問成員變量c:
node.c
情形2
如果程序中定義了一個(gè)指向struct node_t類型的指針p_node如下:
struct node_t node; struct node_t *p_node = &node;
或者在堆上分配了一塊類型為struct node_t的內(nèi)存如下:
struct node_t *p_node= (struct node_t *)malloc(sizeof(struct node_t));
那么我們就可以使用下面的方式來訪問成員變量c:
p_node -> c;
情形3
上述兩種訪問方式都是比較常見的,也是大家所熟悉的,下面我們來探討一種大家不是特別熟悉也不是很常見的情形:
如果程序中只給定了一個(gè)內(nèi)存地址數(shù)值addr_node,且該地址addr_node起始的一段內(nèi)存,指向一塊類型為struct node_t的內(nèi)存,addr_node聲明如下:
unsigned long addr_node;
此時(shí),我們?nèi)绾胃鶕?jù)這塊內(nèi)存地址來訪問成員變量c呢?
由于我們知道了該結(jié)構(gòu)體的起始地址addr_node,所以我們對(duì)其進(jìn)行強(qiáng)制類型轉(zhuǎn)換,從而得到一個(gè)指向該結(jié)構(gòu)體的指針p_node:
struct node_t *p_node = (struct node_t *)addr_node;
接下來我們就可以通過情形2的方式來訪問成員變量c了;
情形3要傳達(dá)的意思是,我們可以通過一個(gè)具體的內(nèi)存地址數(shù)值來訪問我們的結(jié)構(gòu)體成員變量;
關(guān)于情形3的一點(diǎn)說明
((struct node_t *)0)->c
我們通過內(nèi)存地址0來訪問結(jié)構(gòu)體struct node_t成員變量c,但這里面有幾點(diǎn)需要說明一下:
1. 我們并未對(duì)內(nèi)存地址0做過任何內(nèi)存相關(guān)操作,如解引用、賦值等,即內(nèi)存地址編號(hào)0開始的一段內(nèi)存無任何變化;
2. 我們只是利用了編譯器的特性來幫助我們計(jì)算結(jié)構(gòu)體的偏移,僅僅是利用了編譯器的特性來計(jì)算而已;
3. 善于利用編譯器的一些特性來優(yōu)化我們的程序或系統(tǒng);
結(jié)論
本文主要介紹了c語言中關(guān)于訪問結(jié)構(gòu)體成員變量的幾種方式,并對(duì)通過內(nèi)存地址數(shù)值直接訪問結(jié)構(gòu)體成員變量做了說明,解釋了上篇博文中可能產(chǎn)生疑問的一個(gè)問題。
- 詳解C語言的結(jié)構(gòu)體中成員變量偏移問題
- 深入分析C語言中結(jié)構(gòu)體指針的定義與引用詳解
- 淺談C語言中結(jié)構(gòu)體的初始化
- C語言 結(jié)構(gòu)體(Struct)詳解及示例代碼
- C語言中結(jié)構(gòu)體(struct)的幾種初始化方法
- C語言中結(jié)構(gòu)體struct編寫的一些要點(diǎn)解析
- C語言 結(jié)構(gòu)體和指針詳解及簡(jiǎn)單示例
- C語言中的結(jié)構(gòu)體的入門學(xué)習(xí)教程
- C語言 結(jié)構(gòu)體數(shù)組詳解及示例代碼
- C語言中結(jié)構(gòu)體變量私有化詳解
相關(guān)文章
C++ 自由存儲(chǔ)區(qū)是否等價(jià)于堆你知道嗎
自由存儲(chǔ)是C++中通過new與delete動(dòng)態(tài)分配和釋放對(duì)象的抽象概念,而堆(heap)是C語言和操作系統(tǒng)的術(shù)語,是操作系統(tǒng)維護(hù)的一塊動(dòng)態(tài)分配內(nèi)存2021-08-08
C++探索構(gòu)造函數(shù)私有化會(huì)產(chǎn)生什么結(jié)果
C++的構(gòu)造函數(shù)的作?:初始化類對(duì)象的數(shù)據(jù)成員。即類的對(duì)象被創(chuàng)建的時(shí)候,編譯系統(tǒng)對(duì)該對(duì)象分配內(nèi)存空間,并?動(dòng)調(diào)?構(gòu)造函數(shù),完成類成員的初始化。構(gòu)造函數(shù)的特點(diǎn):以類名作為函數(shù)名,?返回類型2022-05-05
OpenCV利用對(duì)比度亮度變換實(shí)現(xiàn)水印去除
OpenCV中去除水印最常用的方法是inpaint,通過圖像修復(fù)的方法來去除水印。本文將介紹另一種方法:利用對(duì)比度亮度變換去除水印,需要的朋友可以參考一下2021-11-11
C語言 實(shí)現(xiàn)遍歷一個(gè)文件夾的所有文件
這篇文章主要介紹了C語言 實(shí)現(xiàn)遍歷一個(gè)文件夾的所有文件的相關(guān)資料,需要的朋友可以參考下2017-01-01

