被問到如下問題:
給定乙個結構體中某個變數位址,可否得到結構體變數的位址?
答案是可以,但是對不同的場合有不同的結果;這與微處理器平台、編譯器的處理不可分割。
首先,對於處理器,大尾端、小尾端的因素必須考慮;
其次:一、ansic標準中並沒有規定,相鄰宣告的變數在記憶體中一定要相鄰。
為了程式的高效性,記憶體對齊問題由編譯器自行靈活處理,這樣導致相鄰的變數之間可能會有一些填充位元組。對於基本資料型別(int char),他們占用的記憶體空間在乙個確定硬體系統下有個確定的值,所以,接下來我們只是考慮結構體成員記憶體分配情況。
1、win32平台下的微軟c編譯器(cl.exe for 80×86)的對齊策略:
1) 結構體變數的首位址能夠被其最寬基本型別成員的大小所整除;
備註:編譯器在給結構體開闢空間時,首先找到結構體中最寬的基本資料型別,然後尋找記憶體位址能被該基本資料型別所整除的位置,作為結構體的首位址。將這個最寬的基本資料型別的大小作為上面介紹的對齊模數。
2) 結構體每個成員相對於結構體首位址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding);
備註:為結構體的乙個成員開闢空間之前,編譯器首先檢查預開闢空間的首位址相對於結構體首位址的偏移是否是本成員的整數倍,若是,則存放本成員,反之,則在本成員和上乙個成員之間填充一定的位元組,以達到整數倍的要求,也就是將預開闢空間的首位址後移幾個位元組。
3) 結構體的總大小為結構體最寬基本型別成員大小的整數倍,如有需要,編譯器會在最末乙個成員之後加上填充位元組(trailing padding)。
備註:結構體總大小是包括填充位元組,最後乙個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最後填充幾個位元組以達到本條要求。
2、gnu gcc編譯器中,遵循的準則有些區別,對齊模數不是像上面所述的那樣,根據最寬的基本資料型別來定。
在gcc中,對齊模數的準則是:對齊模數最大只能是4,也就是說,即使結構體中有double型別,對齊模數還是4,所以對齊模數只能是1,2,4。而且在上述的三條中,第2條裡,offset必須是成員大小的整數倍,如果這個成員大小小於等於4則按照上述準則進行,但是如果大於4了,則結構體每個成員相對於結構體首位址的偏移量(offset)只能按照是4的整數倍來進行判斷是否新增填充。
譬如:struct id
t;根據以上準則,在windows下,使用vc編譯器,sizeof(t)的大小為16個位元組;gnu gcc編譯器則得到12位元組。
二、struct的首位址即為第乙個元素的首位址
如下程式,測試環境,gnu/linux debian, gcc 4.3.2-1-1
1 #include
2 #define struct_offset(id, element) ((
unsigned
long) &((
structid*)
0)->element)
3struct_test
4 ;8
9intmain(void)
10
$ ./a.out
the addrress of first ele of struct is bfb86124
the offset of dd is bfb86128, offset = 4
the start addrress of struct caculated from dd is bfb86124
其中,整個程式中最關鍵的部分就是如何求出結構體中某個成員相對於結構體首位址的偏移量。
這裡的解決方法是:假設存在乙個虛擬位址0,將該位址強制轉換成為該結構體指標型別(struct id*)0。那麼位址0開始到sizeof(struct)-1長度的記憶體區域就可以視為乙個結構體的記憶體。
這樣結構體中任何乙個元素都可以通過對該結構體指標解引用得到。
#define struct_offset(id, element) ((
unsigned
long) &((
structid*)
0)->element)
linux核心裡面的list_entry巨集就是這樣的。
因為有了第1點存在,所以我們就可以只考慮成員的偏移量,這樣思考起來簡單。想想為什麼。
結構體某個成員相對於結構體首位址的偏移量可以通過巨集offsetof()來獲得,這個巨集也在stddef.h中定義,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
例如,想要獲得s中c的偏移量,方法為
size_t pos = offsetof(s, dd);// pos等於4
2) 基本型別是指前面提到的像char、short、int、float、double這樣的內建資料型別,這裡所說的「資料寬度」就是指其sizeof的大小。
由於結構體的成員可以是復合型別,比如另外乙個結構體,所以在尋找最寬基本型別成員時,應當包括復合型別成員的子成員,而不是把復合成員看成是乙個整體。
但在確定復合型別成員的偏移位置時則是將復合型別作為整體看待。
三、有乙個影響sizeof的重要參量還未被提及,那便是編譯器的pack指令。
它是用來調整結構體對齊方式的,不同編譯器名稱和用法略有不同,vc6中通過#pragma pack實現,也可以直接修改/zp編譯開關。
#pragma pack的基本用法為:#pragma pack( n ),n為位元組對齊數,其取值
為1、2、4、8、16,預設是8,如果這個值比結構體成員的sizeof值小,那麼該成員的偏移量應該以此值為準,即是說,結構體成員的偏移量應該取二者的最小值,公式如下:
offsetof( item ) = min( n, sizeof( item ) )
四、還有一點要注意,「空結構體」(不含資料成員)的大小不為0,而是1。
試想乙個「不佔空間」的變數如何被取位址、兩個不同的「空結構體」變數又如何得以區分呢?於是,「空結構體」變數也得被儲存,這樣編譯器也就只能為其分配乙個位元組的空間用於佔位了。
如下:struct s ;
sizeof( s ); // 結果為1
五、含位域結構體的sizeof
位域成員不能單獨被取sizeof值,我們這裡要討論的是含有位域的結構體的sizeof,只是考慮到其特殊性而將其專門列了出來。
c99規定int、unsigned int和bool可以作為位域型別,但編譯器幾乎都對此作了擴充套件,允許其它型別型別的存在。
c語言之結構 struct
struct 結構就是建立乙個模板,類似於陣列,不過它可以擁有不同型別的變數,包括陣列 1,建立結構宣告 舉例 struct book char title 20 char author 20 float value 其中struct是關鍵字,book是結構名,裡面的titlt,author,val...
基礎C語言之Typedef和struct的結合使用
c語言typedef關鍵字 typedef 作為c語言中常見的關鍵字,用法有多種,經常用來改變或者說給一種型別另取乙個名字 include int main n1,n2,n3,narray 10 struct結構體關鍵字用來宣告乙個結構體型別 若在結構體後邊有字串例如上邊這個例子 則代表n1,n2,...
C語言之struct和union分析
1.struct的小秘密 c語言中的struct可以看做變數的集合 struct的問題 空結構體占用多大記憶體?相關測試 include struct ts int main 實驗結果 以上結果是執行在gcc編譯器,如果使用bcc編譯器,則結果不同,此問題屬於c語言中灰色地帶 2.結構體與柔性陣列 ...