C語言struct記憶體佔用問題 (轉)

來源:互聯網
上載者:User

標籤:結束   color   作用   gcc   判斷   強制轉換   檢查   short   申請   

原文:http://hubingforever.blog.163.com/blog/static/17104057920122256134681/

作者寫的很好,摘抄下來以表尊敬!!

一、 ANSI C標準中並沒有規定,相鄰聲明的變數在記憶體中一定要相鄰。
為了程式的高效性,記憶體對齊問題由編譯器自行靈活處理,這樣導致相鄰的變數之間可能會有一些填充位元組。
對於基礎資料型別 (Elementary Data Type)(比如int,char),他們佔用的記憶體空間在一個確定硬體系統下有個確定的值,所以,接下來我們只是考慮結構體成員記憶體配置情況。
1.1、Win32平台下的微軟C編譯器(cl.exe for 80×86)的對齊策略
Win32平台下的微軟C編譯器(cl.exe for 80×86)的對齊策略如下:
1) 結構體變數的首地址能夠被其最寬基本類型成員的大小所整除;
備忘:編譯器在給結構體開闢空間時,首先找到結構體中最寬的基礎資料型別 (Elementary Data Type),然後尋找記憶體位址能被該基礎資料型別 (Elementary Data Type)所整除的位置,作為結構體的首地址。將這個最寬的基礎資料型別 (Elementary Data Type)的大小作為上面介紹的對齊模數。
2) 結構體每個成員相對於結構體首地址的位移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充位元組(internal adding)
備忘:為結構體的一個成員開闢空間之前,編譯器首先檢查預開闢空間的首地址相對於結構體首地址的位移是否是本成員的整數倍,若是,則存放本成員,反之,則在本成員和上一個成員之間填充一定的位元組,以達到整數倍的要求,也就是將預開闢空間的首地址後移幾個位元組。
3) 結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要,編譯器會在最末一個成員之後加上填充位元組(trailing padding)。
備忘:結構體總大小是包括填充位元組,最後一個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最後填充幾個位元組以達到本條要求。
   為了提高CPU的儲存速度,VC對一些變數的起始地址做了“對齊”處理。在預設情況下,VC規定各成員變數存放的起始地址相對於結構體的起始地址的位移量必須為該變數的類型所佔用的位元組數的倍數。各成員變數在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊調整位置,空缺的位元組VC會自動填滿。同時VC為了確保結構的大小為結構的位元組邊界數(即該結構中佔用最大空間的類型所佔用的位元組數)的倍數,所以在為最後一個成員變數申請空間後,還會根據需要自動填滿空缺的位元組。
請看下面的結構:
struct MyStruct
{
double dda1;
char dda;
int type
};
對結構MyStruct採用sizeof會出現什麼結果呢?sizeof(MyStruct)為多少呢?也許你會這樣求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是當在VC中測試上面結構的大小時,你會發現sizeof(MyStruct)為16。你知道為什麼在VC中會得出這樣一個結果嗎?
其實,這是VC對變數儲存的一個特殊處理。為了提高CPU的儲存速度,VC對一些變數的起始地址做了“對齊”處理。在預設情況下,VC規定各成員變數存放的起始地址相對於結構的起始地址的位移量必須為該變數的類型所佔用的位元組數的倍數。下面列出常用類型的
對齊(vc6.0,32位系統)。
類型      對齊(變數存放的起始地址相對於結構的起始地址的位移量)
char      位移量必須為sizeof(char)即1的倍數
short     位移量必須為sizeof(short)即2的倍數
int   位移量必須為sizeof(int)即4的倍數
float 位移量必須為sizeof(float)即4的倍數
double 位移量必須為sizeof(double)即8的倍數
各成員變數在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊調整位置,空缺的位元組VC會自動填滿。同時VC為了確保結構的大小為結構的位元組邊界數(即該結構中佔用最大空間的類型所佔用的位元組數)的倍數,所以在為最後一個成員變數申請空間後,還會根據需要自動填滿空缺的位元組。
下面用前面的例子來說明VC到底怎麼樣來存放結構的。
struct MyStruct
{
double dda1;
char dda;
int type
};
為上面的結構分配空間的時候,VC根據成員變數出現的順序和對齊,先為第一個成員dda1分配空間,其起始地址跟結構的起始地址相同(剛好位移量0剛好為sizeof(double)的倍數),該成員變數佔用sizeof(double)=8個位元組;接下來為第二個成員dda分配空間,這時下一個可以分配的地址對於結構的起始地址的位移量為8,是sizeof(char)的倍數,所以把dda存放在位移量為8的地方滿足對齊,該成員變數佔用 sizeof(char)=1個位元組;接下來為第三個成員type分配空間,這時下一個可以分配的地址對於結構的起始地址的位移量為9,不是sizeof (int)=4的倍數,為了滿足對齊對位移量的約束問題,VC自動填滿3個位元組(這三個位元組沒有放什麼東西),這時下一個可以分配的地址對於結構的起始地址的位移量為12,剛好是sizeof(int)=4的倍數,所以把type存放在位移量為12的地方,該成員變數佔用sizeof(int)=4個位元組;這時整個結構的成員變數已經都分配了空間,總的佔用的空間大小為:8+1+3+4=16,剛好為結構的位元組邊界數(即結構中佔用最大空間的類型所佔用的位元組數sizeof(double)=8)的倍數,所以沒有空缺的位元組需要填充。所以整個結構的大小為:sizeof(MyStruct)=8+1+ 3+4=16,其中有3個位元組是VC自動填滿的,沒有放任何有意義的東西。
下面再舉個例子,交換一下上面的MyStruct的成員變數的位置,使它變成下面的情況:
struct MyStruct
{
char dda;
double dda1;
int type
};
這個結構佔用的空間為多大呢?在VC6.0環境下,可以得到sizeof(MyStruc)為24。結合上面提到的分配空間的一些原則,分析下VC怎麼樣為上面的結構分配空間的。(簡單說明)
struct MyStruct
{
char dda; //位移量為0,滿足對齊,dda佔用1個位元組;
double dda1; //下一個可用的地址的位移量為1,不是sizeof(double)=8 的倍數,需要補足7個位元組才能使位移量變為8(滿足對齊),因此VC自動填滿7個位元組,dda1存放在位移量為8的地址上,它佔用8個位元組。
int type; //下一個可用的地址的位移量為16,是sizeof(int)=4的倍數,滿足int的對齊,所以不需要VC自動填滿,type存放在位移量為16的地址上,它佔用4個位元組。
};
所有成員變數都分配了空間,空間總的大小為1+7+8+4=20,不是結構的節邊界數(即結構中佔用最大空間的類型所佔用的位元組數sizeof (double)=8)的倍數,所以需要填充4個位元組,以滿足結構的大小為 sizeof(double)=8的倍數。 所以該結構總的大小為:sizeof(MyStruc)為1+7+8+4+4=24。其中總的有7+4=11個位元組是VC自動填滿的,沒有放任何有意義的東西。
1.2、GNU GCC編譯器中,遵循的準則有些區別,對齊模數不是像上面所述的那樣。
在 GCC中,對齊模數的準則是:根據最寬的基礎資料型別 (Elementary Data Type)來確定對齊模數,且對齊模數最大隻能是 4,也就是說,即使結構體中有double類型,對齊模數還是4,所以對齊模數只能是1,2,4。而且在上述的三條中,第2條裡,offset必須是成員 大小的整數倍,如果這個成員大小小於等於4則按照上述準則進行,但是如果大於4了,則結構體每個成員相對於結構體首地址的位移量(offset)只能按照 是4的整數倍來進行判斷是否添加填充。
譬如:
struct id
{

char ch;
double dd;

}T;
根據以上準則,在windows下,使用VC編譯器,sizeof(T)的大小為16個位元組;GNU GCC編譯器則得到12位元組。
二、struct的首地址即為第一個元素的首地址
如下程式,測試環境,GNU/Linux Debian, GCC 4.3.2-1-1
#include <stdio.h>
#define STRUCT_OFFSET(id, element) ((unsignedlong) &((structid*)0)->element)
struct _Test
{
    char ch;
    double dd;
 };
int main(void)
{
    struct _Test stru;
    printf("the addrress of first ele of struct is %x\n", &stru.ch);
    unsigned long offset = STRUCT_OFFSET(_Test, dd);
    printf("the offset of dd is %x, offset = %u\n", &stru.dd, offset);
    printf("the start addrress of struct caculated from dd is %x\n", (char*)&stru.dd - offset);
    return 0;
}
執行結果
$ ./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長度的記憶體地區就可以視為一個結構體的記憶體。
這樣結構體中任何一個元素都可 以通過對該結構體指標解引用得到。
由於該結構體的起始地址為0,因此任何一個成員的地址應該等於其相對於結構體起始地址的位移,這也就是計算位移量的方 法:
#define STRUCT_OFFSET(id, element) ((unsignedlong) &((structid*)0)->element)
Linux核心裡面的list_entry宏就是這樣的。
說明:
1) 前面不是說結構體成員的地址是其大小的整數倍,怎麼又說到位移量了呢?
因為有了第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 (n),編譯器將按照n個位元組對齊;
使用偽指令#pragma pack (),取消自訂位元組對齊。
#pragma pack的基本用法為:#pragma pack( n ),n為位元組對齊數,其取值
為1、2、4、8、16,預設是8,如果這個值比結構體成員的sizeof值小,那麼該成員的位移量應該以此值為準,即是說,結構體成員的位移量應該取二者的最小值,公式如下:
offsetof( item ) = min( n, sizeof( item ) )
注意:如果#pragma pack (n)中指定的n大於結構體中最大成員的size,則其不起作用,結構體仍然按照size最大的成員進行對界。
   VC對結構的儲存的特殊處理確實提高CPU儲存變數的速度,但是有時候也帶來了一些麻煩,我們也屏蔽掉變數預設的對齊,自己可以設定變數的對齊。
VC 中提供了#pragma pack(n)來設定變數以n位元組對齊。n位元組對齊就是說變數存放的起始地址的位移量有兩種情況:第一、如果n大於等於該變數所佔用的位元組數,那麼偏 移量必須滿足預設的對齊,第二、如果n小於該變數的類型所佔用的位元組數,那麼位移量為n的倍數,不用滿足預設的對齊。結構的總大小也有個約束條 件,分下面兩種情況:如果n大於所有成員變數類型所佔用的位元組數,那麼結構的總大小必須為佔用空間最大的變數佔用的空間數的倍數;否則必須為n的倍數。
下面舉例說明其用法。
#pragma pack(push) //儲存對齊狀態
#pragma pack(4)//設定為4位元組對齊
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢複對齊狀態
以上結構的大小為16,下面分析其儲存情況,首先為m1分配空間,其位移量為0,滿足我們自己設定的對齊(4位元組對齊),m1佔用1個位元組。接著開始 為 m4分配空間,這時其位移量為1,需要補足3個位元組,這樣使位移量滿足為n=4的倍數(因為sizeof(double)大於n),m4佔用8個位元組。接 著為m3分配空間,這時其位移量為12,滿足為4的倍數,m3佔用4個位元組。這時已經為所有成員變數分配了空間,共分配了16個位元組,滿足為n的倍數。如 果把上面的#pragma pack(4)改為#pragma pack(16),那麼我們可以得到結構的大小為24。
四、“空結構體”(不含資料成員)的大小不為0,而是1。
試想一個“不佔空間”的變數如何被取地址、兩個不同的“空結構體”變數又如何得以區分呢?於是,“空結構體”變數也得被儲存,這樣編譯器也就只能為其分配一個位元組的空間用於佔位了。
如下:
struct S { };
sizeof( S ); // 結果為1

經過我驗證,在VC2008上的確如此。
五、含位域結構體的sizeof
位域成員不能單獨被取sizeof值,我們這裡要討論的是含有位域的結構體的sizeof,只是考慮到其特殊性而將其專門列了出來。
C99規定int、unsigned int和bool可以作為位域類型,但編譯器幾乎都對此作了擴充,允許其它類型類型的存在 六、一些面試題 Intel、微軟等公司曾經出過一道類似的面試題:1. #include <iostream.h>2. #pragma pack(8)3. struct example14. {5. short a;6. long b;7. };8. struct example29. {10. char c;11. example1 struct1;12. short e;    13. };14. #pragma pack() 15. int main(int argc, char* argv[])16. {17. example2 struct2;18. cout << sizeof(example1) << endl;19. cout << sizeof(example2) << endl;20. cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;21. return 0;22. }問程式的輸入結果是什嗎?答案是:8164  程式中第2行#pragma pack (8)雖然指定了對界為8,但是由於struct example1中的成員最大size為4(long變數size為4),故struct example1仍然按4位元組對界,struct example1的size為8,即第18行的輸出結果;  struct example2中包含了struct example1,其本身包含的簡單資料成員的最大size為2(short變數e),但是因為其包含了struct example1,而struct example1中的最大成員size為4,struct example2也應以4對界,#pragma pack (8)中指定的對界對struct example2也不起作用,故19行的輸出結果為16;  由於struct example2中的成員以4為單位對界,故其char變數c後應補充3個空,其後才是成員struct1的記憶體空間,20行的輸出結果為4。 七天、補充
有朋友對上面的內容提出了一下質疑:
質疑1
2012-01-03 01:08 | 回複我 在自己的機子上測試了一下,發現linux上用 gcc(4.6.1)編譯後struct的記憶體對齊和vs2010裡的cl.exe一樣,都是以最大成員長度為準,不像文章所說的gcc裡最大是4個字 節。另外在linux上可以定義空struct,用sizeof擷取長度為0,這也和文章所說的不一致,我在vs2010裡沒法定義空struct,所以 沒有得到結果。 
  
個人覺得可能是新版本的編譯器已經把這部分的規則統一了。
結束!作了擴充,允許其它類型類型的存在

C語言struct記憶體佔用問題 (轉)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.