首先我們來看一段代碼:
struct MyStructOne {
char a;//1字節(jié)
short b;//2字節(jié)
int c;//4字節(jié)
long d;//8字節(jié)
} myStruct1;
struct MyStructTwo {
int c;//4字節(jié)
long d;//8字節(jié)
char a;//1字節(jié)
short b;//2字節(jié)
} myStruct2;
NSLog(@"%lu ----- %lu",sizeof(myStruct1),sizeof(myStruct2));
打印結(jié)果如下:

我們看到這兩個結(jié)構(gòu)體里面的內(nèi)容內(nèi)容一致,只是a、b順序放到了下面,為什么就多占用了8個字節(jié)?并且結(jié)構(gòu)體里面的變量總占用內(nèi)存其實(shí)只要15字節(jié),為什么多占用了一個字節(jié)?這是因?yàn)槌霈F(xiàn)了內(nèi)存對齊。
內(nèi)存對齊規(guī)則
每個特定平臺上的編譯器都有自己的默認(rèn)“對齊系數(shù)”(也叫對齊模數(shù))。程序員可以通過預(yù)編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。在iOS中,Xcode默認(rèn)為#pragma pack(8),即8字節(jié)對齊
1.數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(比如int為4字節(jié),則要從4的整數(shù)倍地址開始存儲)。
2.結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲。(struct a里存有struct b,b里有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲。)
3.收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)?。
附上各類型內(nèi)存占用表
內(nèi)存占用.png
舉例驗(yàn)證
struct MyStructOne {
int c; //4字節(jié) 0 1 2 3
long d;//8字節(jié) (4 5 6 7) 8-15
char a;//1字節(jié) 16
short b;//2字節(jié) (17) 18 19
} myStruct1; //根據(jù)第三條對齊 24
struct MyStructTwo {
int c;//4字節(jié) 0 1 2 3
char a;//1字節(jié) 4
struct MyStructOne struct1; //24字節(jié) (5 6 7) 8-31
short b;//2字節(jié) 32 33
} myStruct2; //根據(jù)第三條對齊 40
測試代碼
long a = (long) &myStruct2.c;
long b = (long) &myStruct2.a;
long c = (long) &myStruct2.struct1.c;
long d = (long) &myStruct2.struct1.d;
long e = (long) &myStruct2.struct1.a;
long f = (long) &myStruct2.struct1.b;
long g = (long) &myStruct2.b;
NSLog(@"%lu ----- %lu",sizeof(myStruct1),sizeof(myStruct2));
NSLog(@"%ld ----- %ld ----- %ld",a,b,c);
NSLog(@"%ld ----- %ld ----- %ld ----- %ld",d,e,f,g);
最終結(jié)果如下:

根據(jù)打印結(jié)果證實(shí)與上面分析結(jié)果一致
-
myStruct1與myStruct2的內(nèi)存大小分別是24和40 - 結(jié)構(gòu)體
myStruct2首地址4294975744,c占用4個字節(jié),a占1個字節(jié),打印地址符合 - 結(jié)構(gòu)體
myStruct1,內(nèi)部最大元素long占用8字節(jié),從偏移量8的地址開始存儲,4294975752符合 - 結(jié)構(gòu)體
myStruct1內(nèi)部元素符合,占用偏移量為8-31的內(nèi)存 - 結(jié)構(gòu)體
myStruct2最后的b元素,從偏移量32位置開始(32剛好是2的整數(shù)倍),4294975776符合;最后根據(jù)第三條,myStruct2內(nèi)部最大成員為myStruct1里的long類型,整個結(jié)構(gòu)體內(nèi)存8字節(jié)對齊,故從33擴(kuò)容到40字節(jié)
為什么要內(nèi)存對齊
內(nèi)存是一片連續(xù)的地址單元,cpu存取數(shù)據(jù)的時候是一塊一塊的進(jìn)行存取的,塊的大小可以是2,4,8,16字節(jié)大小。每次內(nèi)存存取都會產(chǎn)生一個固定的內(nèi)存開銷,減少內(nèi)存存取次數(shù)將可以提升程序的性能。所以cpu一般會以2/4/8/16/32字節(jié)為單位進(jìn)行存取。上述這些存取單位也就是塊大小被稱為(memory access granularity)內(nèi)存存取粒度。
總結(jié)
了解完這些之后,當(dāng)我們再聲明結(jié)構(gòu)體的時候就可以合理安排內(nèi)部數(shù)據(jù)的順序,從而使其占用更少的內(nèi)存。
:Vc,Vs等編譯器默認(rèn)是#pragma pack(8),所以測試我們的規(guī)則會正常;而gcc默認(rèn)是#pragma pack(4),并且gcc只支持1,2,4對齊。套用三原則里計算的對齊值是不能大于#pragma pack指定的n值。
