OC底層原理三:內(nèi)存對齊分析

獲取內(nèi)存大小

上一篇我們簡單的提了下內(nèi)存字節(jié)對齊以及為什么要內(nèi)存字節(jié)對齊,那么我們首先看下有什么方式可以獲取內(nèi)存大小。

  • sizeof(type)主要是獲取數(shù)據(jù)類型占用的內(nèi)存大小。
    我們先打印下各數(shù)據(jù)類型占用的內(nèi)存情況:
 //基本數(shù)據(jù)類型
 NSLog(@"char內(nèi)存大小為%lu",sizeof(char));//char:1
 NSLog(@"BOOL內(nèi)存大小為%lu",sizeof(BOOL));  // BOOL:1
 NSLog(@"short內(nèi)存大小為%lu",sizeof(short));  // short:2
 NSLog(@"int內(nèi)存大小%lu",sizeof(int));  // int:4
 NSLog(@"float內(nèi)存大小為%lu",sizeof(float));  // float:4
 NSLog(@"long內(nèi)存大小為%lu",sizeof(long));  // long:8
 NSLog(@"double內(nèi)存大小為%lu",sizeof(double));  // double:8
  //結(jié)構(gòu)體類型
  struct Struct1{
       int age;
   }test1;           //4
  struct Struct2{
       int age;
       float point;
   }test2;          //8
   struct Struct3{
       double d;
       int age;
       float point;
   }test3;          //16
  NSLog(@"test1內(nèi)存大小為%lu,test2內(nèi)存大小為%lu,test3內(nèi)存大小為%lu,",sizeof(test1),sizeof(test2),sizeof(test3));    
內(nèi)存大小.png
  • class_getInstanceSize方法可以獲取對象在堆空間中實(shí)際占用的內(nèi)存情況,只能傳入對象。
  • malloc_size可以獲取實(shí)際為對象開辟的內(nèi)存空間大小情況,只能傳入對象。實(shí)際分配的和實(shí)際占用的內(nèi)存大小并不相同。

內(nèi)存對齊的理解

我們已經(jīng)知道cpu讀取內(nèi)存是以為單位進(jìn)行讀取,如果讀取的內(nèi)容正好在一個塊內(nèi),那么一次就能讀取。如果讀取的內(nèi)容分布在了兩個塊,那么就需要至少讀兩次,還要做額為的剔除合并的處理,降低了讀取效率。所以讀取的內(nèi)容的末端可以認(rèn)為這塊內(nèi)容的邊界,那么如何用最少的次數(shù)讀取到內(nèi)容的處理就是內(nèi)存對齊。

內(nèi)存對齊規(guī)則

  • 數(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ù)倍地址開始存儲。

  • 結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲.(struct a里存有struct b,b里有int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲)。

  • 補(bǔ)充:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)齊。

總之,內(nèi)存對齊原則就是:min(m,n) //m為開始的位置,n為所占位數(shù)。當(dāng)m是n的整數(shù)倍時,條件滿足;否則m位空余,繼續(xù)取下一位m+1,繼續(xù)min算法:


內(nèi)存對齊原則.png

結(jié)構(gòu)體對齊

下面我們通過結(jié)構(gòu)體對齊的例子來理解內(nèi)存對齊原則。

定義兩個結(jié)構(gòu)體:

struct OCStruct1{
    double a; //8  0-7
    char b; //1   8
    int c; //4    12-15
    short d; //2  16 17
}struct1;                         結(jié)構(gòu)體內(nèi)存大小為24

struct OCStruct2{
    double a; //8 0-7
    int c;   //4  8-11
    char b; //1 12
    short d; //2  14-15    
}struct2;                        結(jié)構(gòu)體內(nèi)存大小為16

NSLog(@"struct1=%lu, struct2=%lu",sizeof(test1),sizeof(test2));

打印后可以看出struct1內(nèi)存大小為24,struct2內(nèi)存大小為16。擁有相同的元素,內(nèi)存大小卻不一樣,為什么呢?我們根據(jù)內(nèi)存對齊原則來進(jìn)行計(jì)算struct1的內(nèi)存大小:

  • 先存放double a,占8個字節(jié),根據(jù)min(0,8),從0開始存放8個字節(jié),即0-7位存放a。
  • 然后存放char b,占1個字節(jié),起始位置為8,根據(jù)min(8,1),從9開始存放1個字節(jié),即8存放b
  • 然后存放int c,占4個字節(jié),起始位置為9,根據(jù)min(9,4),4不能被9整除,依次往后找,找到12,從12開始存放4個字節(jié),即12-15存放c。
  • 最后存放short c,占2個字節(jié),起始位置為16,根據(jù)min(16,2),從16開始存放2個字節(jié),即16-17存放d。
  • 最后根據(jù)補(bǔ)充原則,占用內(nèi)存總大小必須是最大變量占用的內(nèi)存大小的整數(shù)倍,在此例子里即必須是8的倍數(shù),所以內(nèi)存大小最終確定為24。

同理計(jì)算struct2的內(nèi)存大小:

  • 先存放double a,占8個字節(jié),根據(jù)min(0,8),從0開始存放8個字節(jié),即0-7位存放a
  • 然后存放int c,占4個字節(jié),起始位置為8,根據(jù)min(8,4),從8開始存放4個字節(jié),即8-11存放'c'。
  • 然后存放char b,占1個字節(jié),起始位置為12,根據(jù)min(12,1),從12開始存放1個字節(jié),即12存放'b'。
  • 最后存放short d,占2個字節(jié)d,起始位置為13,根據(jù)min(16,2),2不能被13整除,繼續(xù)往后查找,從14開始存放2個字節(jié),即14-15存放'd'。
  • 最后根據(jù)補(bǔ)充原則,占用內(nèi)存總大小必須是最大變量占用的內(nèi)存大小的整數(shù)倍,在此例子里即必須是8的倍數(shù),所以內(nèi)存大小最終確定為16。

內(nèi)存優(yōu)化

從上面的例子我們可以看出結(jié)構(gòu)體的內(nèi)存大小和結(jié)構(gòu)體內(nèi)成員的順序是有關(guān)系的,根據(jù)這點(diǎn)我們在寫結(jié)構(gòu)體的時候可以進(jìn)行屬性重排,內(nèi)存優(yōu)化。

結(jié)構(gòu)體成員按照內(nèi)存從大到小的順序排列的話,只需要增加少量的內(nèi)存空余就可以完成內(nèi)存對齊。所以內(nèi)存對齊并不是一味的追求用空間換時間,能優(yōu)化的空間還是最好優(yōu)化的。

結(jié)構(gòu)體嵌套的內(nèi)存大小

下面我們根據(jù)內(nèi)存對齊原則看下結(jié)構(gòu)體嵌套結(jié)構(gòu)體的情況:

struct OCStruct3{
    char b;
    int c;
    double a;
    short d;
}struct3;

struct OCStruct4{
    double a;
    int c;
    char b;
    short d;
    struct OCStruct3 stuct3;
}struct4;

NSLog(@"struct3內(nèi)存大小為%lu,struct4內(nèi)存大小為%lu",sizeof(struct3),sizeof(struct4));

打印后的struct4內(nèi)存大小為40,現(xiàn)在我們計(jì)算下是不是40:

結(jié)構(gòu)體的嵌套等同于將從子結(jié)構(gòu)體中將成員變量直接放到父成員變量中:

struct OCStruct4{
            double a;
            int c;
            char b;
            short d;
            struct OCStruct3{
                char b;
                int c;
                double a;
                short d;
            }struct3;
        }struct4;
  • 先存放double a,占8個字節(jié),根據(jù)min(0,8),從0開始存放8個字節(jié),即0-7位存放a。
  • 然后存放int c,占4個字節(jié),起始位置為8,根據(jù)min(8,4),從8開始存放4個字節(jié),即8-11存放'c'。
  • 然后存放char b,占1個字節(jié),起始位置為12,根據(jù)min(12,1),從12開始存放1個字節(jié),即12存放'b'。
  • 然后存放short d,占2個字節(jié)d,起始位置為13,根據(jù)min(16,2),2不能被13整除,繼續(xù)往后查找,從14開始存放2個字節(jié),即14-15存放'd'。
  • 然后存放結(jié)構(gòu)體struct3里的char b,占1個字節(jié),根據(jù)內(nèi)存對齊原則第二條必須從結(jié)構(gòu)體內(nèi)最大元素內(nèi)存大小的整數(shù)倍,所以應(yīng)該是8的倍數(shù),而16正好被8整除,所以從16開始存放1個字節(jié),即16存放struct3里的b
  • 然后存放結(jié)構(gòu)體struct3里的int c,占4個字節(jié),起始位置為17,根據(jù)min(17,4),17不能被4整除,往后查找,從20開始存放4個字節(jié),即20-23存放struct3里的c。
  • 然后存放結(jié)構(gòu)體struct3里的double a,占8個字節(jié),起始位置為24,根據(jù)min(24,8),從24開始存放8個字節(jié),即24-31存放struct3里的a
  • 然后存放結(jié)構(gòu)體struct3里的short d,占2個字節(jié),起始位置為32,根據(jù)min(32,2),從32開始存放2個字節(jié),即32-33存放struct3里的d。
  • 最后根據(jù)補(bǔ)充原則,占用內(nèi)存總大小必須是最大變量占用的內(nèi)存大小的整數(shù)倍,在此例子里即必須是8的倍數(shù),所以內(nèi)存大小最終確定為40。

總之,內(nèi)存對齊的作用不僅是便于cpu快速訪問,同時合理的利用內(nèi)存對齊可以有效地節(jié)省存儲空間。

?著作權(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)容