iOS底層-4:內(nèi)存字節(jié)對齊

首先我們來看一段代碼:

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é)果如下:


image.png

我們看到這兩個結(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é)果如下:


image.png

根據(jù)打印結(jié)果證實(shí)與上面分析結(jié)果一致

  • myStruct1myStruct2的內(nèi)存大小分別是2440
  • 結(jié)構(gòu)體myStruct2首地址4294975744c占用4個字節(jié),a1個字節(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值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容