2014-01-30 94 views
36

我認爲在C中是不可能的,但要求驗證這一點。是否有可能在沒有這種類型的實變量的情況下在結構成員之間進行算術運算例如:沒有結構對象的C指針算術

typedef struct _s1 
{ 
    int a; 
    int b; 
    int c; 
} T1; 

我想查看「c」成員與結構開始相比的偏移量。這很容易,如果我有變數:

T1 str; 

int dist = (int)&str.c - (int)&str; 

但我的結構太大,它沒有成員在RAM(只在EEPROM中)。我想做一些地址計算,但不想定義RAM成員。我可以用結構指針而不是結構變量來完成這項工作(它只需要4個字節),但這種情況對我來說很有意思。

+8

定義假指針,看到了什麼? – Cthulhu

+3

這可能已經在這裏得到了答案,並在其他地方討論過數十億次。我想答案應該是谷歌搜索了。你有Google嗎? –

+0

如果你的結構真的很大,那麼將它的地址和它的一個成員的地址轉換爲int可能會導致你的悲傷(指針是4/8字節,int只能是2字節大小,但大多數實現使用4個字節) –

回答

56

使用offsetof可以使成員之間的計算,而不必弄個該類型的變量。所有你需要的是類型本身和成員的名字。

爲什麼普通計算可能無法解決:數據對齊。你不知道你的編譯器會在你的結構中填充的填充量,如果你改變了結構,這會導致非常微妙的錯誤,或者使看起來正確的計算錯誤。

12

stddef.h有一個叫做offsetof的宏,它給出了一個結構起始位置的偏移量。

size_t t = offsetof(T1 , c) ; 

這樣你就不必實際聲明任何結構體變量。

8

在標題stddef.h中有一個宏; offsetof。你可以用它在你的結構是這樣的:

int dist = (int)offsetof(T1, c); 
25

首先,您將個人地址轉換爲int s。我不認爲這是一個好主意。一個int保證至少是大小爲或更大的字節,地址/指針通常是4或8個字節。將這些投射到int只是不對。如果我將它們投射到任何東西上,我可能會使用3232位系統的unsigned long,以及64位的unsigned long long。我會用後者來保證安全。
也就是說,我不會投射地址,只需使用offsetof宏。

添加#include <stddef.h>到您的文件,並使用offsetof宏,它蒙上偏移值size_t類型,而不是一個int,請注意。它的工作原理簡單地通過使用0作爲該結構的內存地址,然後返回給定成員的地址,給實際偏移:

size_t offset = offsetoff(T1, c); 
        //struct, member 

正如扎克在他的評論中指出,下一部分的答案有點不相關,但對於它包含的鏈接,爲了完整起見,我只是將它留在這裏 - 因爲所有實現都需要offsetof

自己定義宏,如果你不有stddef.h由於某種原因(這應該是,因爲offsetof一段時間以來一直是標準的一部分:C89及以上),就像所以:

#ifndef offsetof 
#define offsetof(s, m) ((size_t)&(((s *) 0)->m)) 
#endif 

這應該做的伎倆,但要注意:此實現可能會導致不確定的行爲。 How and why is explained here
我已經採用了the stddef.h source I found here以上的offsetof定義,因此您可以下載該文件並使用它,而不是在自己的文件中定義宏,但請記住,您使用的offsetof不是100%可靠的。

無論如何,這是夠了,在谷歌更多的信息,但這裏有一個鏈接夫婦:

+6

定義'offsetof'有點危險。你展示的實現是一個未定義行爲的邊界情況(我可以爲此找到一個引用),但是可以爲實現者做這件事,因爲他們知道(或必須確保)編譯器的行爲。 – pmr

+1

@pmr:在我的回答中增加了一些鏈接,thx指向我。我不知道'偏移'的怪癖(我的C「知識」的主要來源是K&R,它根本沒有提到宏)。無論如何,+1指向與OP –

+2

的數據對齊的答案沒有任何理由無法提供'stddef.h',其中'offsetof'現在 - 所有實現都需要它,獨立或其他,自C89以來。 – zwol

16

您可以使用offsetof定義stddef.h

我知道這超出了你所問,但有一個有趣的使用offsetof用於例如在Linux內核代碼中,container_of宏。 container_of可以回到你父結構的地址,給定一個指向其成員之一:

#define container_of(ptr, type, member) ({      \ 
     const typeof(((type *)0)->member) *__mptr = (ptr); \ 
     (type *)((char *)__mptr - offsetof(type,member));}) 

,您可以使用,例如:

// pointer_to_c points to a member of the struct, c in this case 
// and you want to get the address of the container 
T1 *container; 
container = container_of(pointer_to_c, T1, c); 
+0

我喜歡'container_of'宏,但'(char *)'類型轉換對我來說看起來有點像_legacy_,如果你不介意我的話。我認爲現在,我們將使用void pointer –

+2

@EliasVanOotegem我認爲(char *)是必要的,因爲後面的__mptr用於指針算術。它需要通過offsetof(...)字節來減少。你不應該使用算術上的viud指針http://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c – fede1024

+1

好點...我剛看到'(char *)__ mptr',沒有停下來考慮'void * - size_t'的含義:) –