鄭重聲明:本文是筆者根據個人理解所寫,錯誤難免,歡迎拍磚!
可以任意轉載、修改,轉載時是否標明出處,隨君而定!
請說出如下2種方式,哪種更好,為什么?
方式一:
void foo(int a, float b, char* ch, double d, float f);
方式二:
struct A { int a; float b; char ch[5]; double d; float f; }; void foo(A* pa);
咋一看,不知道這題想要考什么,無從下手。其實該題是檢查考生對於內存對齊的理解。下面我們先看看關於內存的一些知識。
什么是字節對齊,為什么要對齊?
現代計算機中內存空間都是按照byte划分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址訪問,這就需要各種類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
對齊的作用和原因:各個硬件平台對存儲空間的處理上有很大的不同。一些平台對某些特定類型的數據只能從某些特定地址開始存取。比如有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那么在這種架構下編程必須保證字節對齊.其他平台可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對數據存放進行對齊,會在存取效率上帶來損失。比如有些平台每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀周期,並對兩次讀出的結果的高低字節進行拼湊才能得到該32bit數據。顯然在讀取效率上下降很多。
對齊規則
每個特定平台上的編譯器都有自己的默認“對齊系數”(也叫對齊模數,32位gcc 4.7上默認為8,32位VS2010上默認為8)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊系數”。
規則:
1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之后,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
3、結合1、2可推斷:當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。
筆者總結:當用sizeof求結構的大小時,第一個數據大小始終為該數據的sizeof大小(即偏移位始終為0),與對齊系數無關。下一個數據的仍為其sizeof的大小,但偏移位為min(數據的sizeof大小,對齊系數)。最后對結構本身進行對齊,即結構的sizeof大小為min(結構中最大數據成員長度,對齊系數)的整數倍。注意: 該規則不適用於有設置位域的結構。
如果我們不想編譯器自動為我們添加補齊位,可以將對齊系數設為1,即
1 #pragma pack(push, 1) 2 // code... 3 #pragma pack(pop)
實例1(32位GCC 4.7):
#include <iostream> #include <cstddef> using namespace std; //#pragma pack(push, 4) struct A { int a; float b; char c; double d; int *pa; char *pc; }; //#pragma pack(pop) int main() { cout << offsetof(A, a) << endl; cout << offsetof(A, b) << endl; cout << offsetof(A, c) << endl; cout << offsetof(A, d) << endl; cout << offsetof(A, pa) << endl; cout << offsetof(A, pc) << endl; cout << sizeof(A) << endl; }
輸出結果:
0 4 8 16 24 28 32
分析結果:這里默認對齊系數為8,讀者可以根據上面的規則推算一下,sizeof(A) = 4(a) + 4(b) + 1(c) + 7(偏移位) + 8(d) + 4(pa) + 4(pc) = 32。注意,這里別忘了結構本身的對齊,上面各個數據成員偏移后的大小剛好是對齊系數8的整數倍,就不需要再進行偏移了。如果將上面的注釋去掉,即將默認對齊系數改為4,想想sizeof(A)的大小?sizeof(A) = 4(a) + 4(b) + 1(c) + 3(偏移位) + 8(d) + 4(pa) + 4(pc) = 28。
這里的offsetof是查看數據成員在結構中的偏移位,定義在cstddef文件中。當你#pragma pack(push,n)改變默認對象系數時,n只能取1、2、4、8、16,將你想要進行內存對齊的結構放入#pragma pack(push, n) ... #pragma pack(pop)之間,這樣可以防止將其他的結構也進行內存對齊。
現在回到上面的面試題,方式一調用不存在內存對齊的問題,傳入的數據大小就是其本來的數據大小,而方式二就不一樣了,由於內存對齊的原因,將導致傳入的結構大小為32(32位GCC 4.7)個字節,sizeof(A) = 4(a) + 4(b) + 5(ch) + 3(偏移) + 8(d) + 4 + 4(結構本身對齊偏移) = 32。顯然方式一的效果比較高。