轉自bigloomy CSDN部落格:http://blog.csdn.net/bigloomy/article/details/6633008
學習筆記(和前篇《C/C++刁鑽問題各個擊破之細說sizeof》一起):
1. #pragma value (value)後面的代碼的對齊值取value指定值和預設值中較小的那個;
2. 在TC2.0中編譯結果看:預設採用單位元組對齊的方式,即不會自動填滿空位元組;
3. Gcc編譯中,sizeof對void操作結果為1,但在vc6.0 下無法編譯;
4. 測試發現:在VC 6.0下,結構體相同類型的位域成員比較緊湊,不同類型的則考慮對齊;
如
typedef struct{ inta:1; intb : 4; charc : 4;}MyStruct;//長度為8typedef struct{ inta:1; intb : 4; intc : 4;}MyStruct;//長度為4typedef struct{ chara:1; charb : 4; charc : 4;}MyStruct;//長度為2
而TC2.0隻支援對int和unsigned int類型劃分位域;
原文內容如下:
可能有不少讀者會問,位元組對齊有必要拿出來單獨寫一篇部落格嘛?我覺得是很有必要,但是它卻是被很多人所忽視的一個重點。那麼我們使用位元組對齊的作用和原因是什麼呢?由於硬體平台之間對儲存空間的處理上是有很大不同的,一些平台對某些特定類型的資料只能從某些特定地址開始存取,如通常有些架構的CPU要求在編程時必須保證位元組對齊,否則訪問一個沒有進行位元組對齊的變數的時候會發生錯誤。而有些平台可能沒有這種情況,但是通常的情況是如果我們編程的時候不按照適合其平台要求對資料存放進行對齊,會在存取效率上帶來損失。比如有些平台每次讀都是從偶地址開始,如我們操作一個int型資料,如果存放在偶地址開始的地方,那麼一個讀周期就可以讀出,而如果存放在奇地址開始的地方,就可能會需要2個讀周期,兩個周期讀取出來的位元組我們還要對它們進行高低位元組的拼湊才能得到該int型資料,從而使得我們的讀取效率較低,這也從側面反映出了一個問題,就是我們很多時候是在犧牲空間來節省時間的。
可能看了上面的講解你還是不太明白,那我們再來看一次什麼是位元組對齊呢? 我們現在的電腦中記憶體空間都是按照位元組來進行劃分的,從理論上來講的話似乎對任何類型的變數的訪問可以從任何地址開始,然而值得注意的就是,實際情況下在訪問特定變數的時候經常在特定的記憶體位址訪問,從而就需要各種類型的資料按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
按照預先的計劃安排,這次應該是寫《C語言的那些小秘密之鏈表(三)》的,但是我發現如果直接開始講解linux核心鏈表的話,可能有些地方如果我們不在此做一個適當的講解的話,有的讀者看起來可能難以理解,所以就把位元組對齊挑出來另寫一篇部落格,我在此儘可能的講解完關於位元組對齊的內容,希望我的講解對你有所協助。
在此之前我們不得不提的一個操作符就是sizeof,其作用就是返回一個對象或者類型所佔的記憶體位元組數。我們為什麼不在此稱之為sizeof()函數呢?看看下面一段代碼:
view plain
1. #include <stdio.h>
2.
3. void print()
4. {
5. printf("hello world!\n");
6. return ;
7. }
8. void main()
9. {
10. printf("%d\n",sizeof(print()));
11. return ;
12. }
這段代碼在linux環境下我採用gcc編譯是沒有任何問題的,對於void類型,其長度為1,但是如果我們在vc6下面啟動並執行話話就會出現illegal sizeof operand錯誤,所以我們稱之為操作符更加的準確些,既然是操作符,那麼我們來看看它的幾種使用方式:[LGZ1]
1、sizeof( object); // sizeof(對象 );
2、 sizeof( type_name ); // sizeof( 類型 );
3、sizeofobject; // sizeof 對象; 通常這種寫法我們在代碼中都不會使用,所以很少見到。
下面來看段代碼加深下印象:
view plain
1. #include <stdio.h>
2.
3. void main()
4. {
5. int i;
6. printf("sizeof(i):\t%d\n",sizeof(i));
7. printf("sizeof(4):\t%d\n",sizeof(4));
8. printf("sizeof(4+2.5):\t%d\n",sizeof(4+2.5));
9. printf("sizeof(int):\t%d\n",sizeof(int));
10. printf("sizeof 5:\t%d\n",sizeof 5);
11. return ;
12. }
運行結果為:
view plain
1. sizeof(i): 4
2. sizeof(4): 4
3. sizeof(4+2.5): 8
4. sizeof(int): 4
5. sizeof 5: 4
6. Press any key to continue
從運行結果我們可以看出上面的幾種使用方式,實際上,sizeof計算對象的大小也是轉換成對物件類型的計算,也就是說,同種類型的不同對象其sizeof值都是一樣的。從給出的代碼中我們也可以看出sizeof可以對一個運算式求值,編譯器根據運算式的最終結果類型來確定大小,但是一般不會對錶達式進行計算或者當運算式為函數時並不執行函數體。如:
view plain
1. #include <stdio.h>
2. int print()
3. {
4. printf("Hello bigloomy!");
5. return 0;
6. }
7. void main()
8. {
9. printf("sizeof(print()):\t%d\n",sizeof(print()));
10. return ;
11. }
運行結果為:
view plain
1. sizeof(print()): 4
2. Press any key to continue
從結果我們可以看出print()函數並沒有被調用。
接下來我們來看看linux核心鏈表裡的一個宏:
#defineoffsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
對這個宏的講解我們大致可以分為以下4步進行講解:
1、( (TYPE *)0 ) 0地址強制 "轉換" 為 TYPE結構類型的指標;
2、((TYPE *)0)->MEMBER 訪問TYPE結構中的MEMBER資料成員;
3、&( ( (TYPE *)0 )->MEMBER)取出TYPE結構中的資料成員MEMBER的地址;
4、(size_t)(&(((TYPE*)0)->MEMBER))結果轉換為size_t類型。
宏offsetof的巧妙之處在於將0地址強制轉換為 TYPE結構類型的指標,TYPE結構以記憶體空間首地址0作為起始地址,則成員地址自然為位移地址。可能有的讀者會想是不是非要用0呢?當然不是,我們僅僅是為了計算的簡便。也可以使用是他的值,只是算出來的結果還要再減去該數值才是位移地址。來看看下面的代碼:
view plain
1. #include <stdio.h>
2.
3. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)
4.
5. typedef struct stu1
6. {
7. int a;
8. int b;
9. }stu1;
10.
11. void main()
12. {
13. printf("offsetof(stu1,a):\t%d\n",offsetof(stu1,a)-4);
14. printf("offsetof(stu1,b):\t%d\n",offsetof(stu1,b)-4);
15. }
運行結果為:
view plain
1. offsetof(stu1,a): 0
2. offsetof(stu1,b): 4
3. Press any key to continue
為了讓讀者加深印象,我們這裡在代碼中沒有使用0,而是使用的4,所以在最終計算出的結果部分減去了一個4才是位移地址,當然實際使用中我們都是用的是0。
懂了上面的宏offsetof之後我們再來看看下面的代碼:
view plain
1. #include <stdio.h>
2.
3. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
4.
5. typedef struct stu1
6. {
7. int a;
8. char b[1];
9. int c;
10. }stu1;
11.
12. void main()
13. {
14. printf("offsetof(stu1,a):\t%d\n",offsetof(stu1,a));
15. printf("offsetof(stu1,b):\t%d\n",offsetof(stu1,b));
16. printf("offsetof(stu1,c):\t%d\n",offsetof(stu1,c));
17. printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
18. }
運行結果為:
view plain
1. offsetof(stu1,a): 0
2. offsetof(stu1,b): 4
3. offsetof(stu1,c): 8
4. sizeof(stu1) : 12
5. Press any key to continue
對於位元組對齊不瞭解的讀者可能有疑惑的是c的位移量怎麼會是8和結構體的大小怎麼會是12呢?因該是sizeof(int)+sizeof(char)+sizeof(int)=9。其實這是編譯器對變數儲存的一個特殊處理。為了提高CPU的儲存速度,編譯器對一些變數的起始地址做了對齊處理。在預設情況下,編譯器規定各成員變數存放的起始地址相對於結構的起始地址的位移量必須為該變數的類型所佔用的位元組數的倍數。現在來分析下上面的代碼,如果我們假定a的起始地址為0,它佔用了4個位元組,那麼接下來的空閑地址就是4,是1的倍數,滿足要求,所以b存放的起始地址是4,佔用一個位元組。接下來的空閑地址為5,而c是int變數,佔用4個位元組,5不是4的整數倍,所以向後移動,找到離5最近的8作為存放c的起始地址,c也佔用4位元組,所以最後使得結構體的大小為12。現在我們再來看看下面的代碼:
view plain
1. #include <stdio.h>
2.
3. typedef struct stu1
4. {
5. char array[7];
6. }stu1;
7.
8. typedef struct stu2
9. {
10. double fa;
11. }stu2;
12.
13. typedef struct stu3
14. {
15. stu1 s;
16. char str;
17. }stu3;
18.
19. typedef struct stu4
20. {
21. stu2 s;
22. char str;
23. }stu4;
24.
25. void main()
26. {
27. printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
28. printf("sizeof(stu2) :\t%d\n",sizeof(stu2));
29. printf("sizeof(stu3) :\t%d\n",sizeof(stu3));
30. printf("sizeof(stu4) :\t%d\n",sizeof(stu4));
31. }
運行結果為:
view plain
1. sizeof(stu1) : 7
2. sizeof(stu2) : 8
3. sizeof(stu3) : 8
4. sizeof(stu4) : 16
5. Press any key to continue
分析下上面我們的運行結果,重點是struct stu3和struct stu4,在struct stu3中使用的是一個位元組對齊,因為在stu1和stu3中都只有一個char類型,在struct stu3中我們定義了一個stu1類型的 s,而stu1所佔的大小為7,所以加上加上接下來的一個位元組str,sizeof(stu3)為8。在stu4中,由於我們定義了一個stu2類型的s,而s是一個double類型的變數,佔用8位元組,所以接下來在stu4中採用的是8位元組對齊。如果我們此時假定stu4中的s從地址0開始存放,佔用8個位元組,接下來的空閑地址就是8,根據我們上面的講解可知剛好可以在此存放str。所以變數都分配完空間後stu4結構體所佔的位元組數為9,但9不是結構體的邊界數,也就是說我們要求分配的位元組數為結構體中佔用空間最大的類型所佔用的位元組數的整數倍,在這裡也就是double類型所佔用的位元組數8的整數倍,所以接下來還要再分配7個位元組的空間,該7個位元組的空間沒有使用,由編譯器自動填滿,沒有存放任何有意義的東西。
當然我們也可以使用先行編譯指令#pragma pack (value)[LGZ2] 來告訴編譯器,使用我們指定的對齊值來取代預設的。接下來我們來看看一段代碼。
view plain
1. #include <stdio.h>
2.
3. #pragma pack (1) /*指定按1位元組對齊*/
4.
5. typedef union stu1
6. {
7. char str[10];
8. int b;
9. }stu1;
10.
11. #pragma pack () /*取消指定對齊,恢複預設對齊*/
12.
13. typedef union stu2
14. {
15. char str[10];
16. int b;
17. }stu2;
18.
19. void main()
20. {
21. printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
22. printf("sizeof(stu2) :\t%d\n",sizeof(stu2));
23. }
運行結果為:
view plain
1. sizeof(stu1) : 10
2. sizeof(stu2) : 12
3. Press any key to continue
現在來分析下上面的代碼。由於之前我們一直都在使用struct,所以在這裡我們特地例舉了一個union的代碼來分析下,我們大家都知道union的大小取決於它所有的成員中佔用空間最大的一個成員的大小。由於在union stu1中我們使用了1位元組對齊,所以對於stu1來說佔用空間最大的是char str[10]類型的數組,,其值為10。為什麼stu1為10而stu2卻是12呢?因為在stu2的上面我們使用了#pragma pack () ,取消指定對齊,恢複預設對齊。所以由於stu2其中int類型成員的存在,使stu2的對齊變成4位元組對齊,也就是說,stu2的大小必須在4的對界上,換句話說就是stu2的大小要是4的整數倍,所以佔用的空間變成了12。