內存對齊原理

獲取內存大小

第一種:sizeof

The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type(including aggregate types). This keyword returns a value of type size_t.

  • sizeof是C/C++中的一個操作符(operator),簡單的說其作用就是返回一個對象或者類型所占的內存字節數。
  • 功能:對象或類型所佔的字節數

第二種:class_getInstanceSize

  • 是runtime提供的api,用於獲取類的實例對象所佔用的內存大小 ,並返回具體的字節數,其本質就是獲取實例對象中成員變量的內存大小
  • 分析可以看此篇cls->instanceSize:計算所需內存大小

alloc&init原理

第三種:malloc_size

  • 獲取系統實際分配的內存大小

測試

  • 首先創建一個類
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end
  • main.m文件
#import 
#import "AppDelegate.h"
#import "Person.h"
#import 
#import 

int main(int argc, char * argv[]) {
 @autoreleasepool {
     Person *p1 = [[Person alloc] init];
     NSLog(@"p1对象类型占用的内存大小:%lu",sizeof(p1));
     NSLog(@"p1对象实际占用的内存大小:%lu",class_getInstanceSize([p1 class]));
     NSLog(@"p1对象实际分配的内存大小:%lu",malloc_size((__bridge const void *)(p1)));
 }
 return 0;
}
  • 打印結果
p1对象类型占用的内存大小:8
p1对象实际占用的内存大小:24
p1对象实际分配的内存大小:32

為什麼

  • 為什麼p1對象類型是佔用8字節?
    • 答:因為對象的本質是結構體指針,而指針佔的是8個字節。
  • 為什麼對象實際佔用24字節,不是20嗎?isa(8字節)+NSString *name(8字節)+int age(4字節)
    • 答:確實字節大小共為20字節,但是依照內存對齊原則進行了字節補齊,所以補齊到了24字節(3個8字節放得下)。
  • 為什麼對象實際分配是32字節?
    • 答:malloc_size是系統分配的大小,以16字節對齊,大小20個字節要32字節(兩個16字節放得下)。

結構體內存對齊規則

  • 每個特定平台上的編譯器都有自己的默認“對齊係數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一係數,其中的n就是你要指定的“对齐系数”。在iOS中,Xcode默認為#pragma pack(8),即8字節對齊

規則一

  • 數據成員對齊規則:結構體或者聯合體的第一個成員放在offset為0的地方,以後每個數據成員存儲的起始位置要從該成員的大小或者該成員的子成員的大小的整數倍開始

規則二

  • 結構體作為成員:如果一個結構體A中有結構體B作為子成員,B中存放有char,int,double等元素,那麼B應該從double也就是8的整數倍開始存儲

規則三

  • 結構體的總體大小,即sizeof的結果,必須是其內部最大成員的整數倍,不足的需要補齊.

內存對齊的原因

  • 效能提升

    未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。最重要的是提高內存系統的性能。

  • 對應各家平台

    不是所有的硬件平台都能訪問任意地址上的任意數據的;某些硬件平台只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

數據類型對應的字節數表格

數據類型對應的字節數表格.png

範例一:

struct StructA {
    double a;   // 8 (0-7)
    char b;     // 1 [8 1] (8)
    int c;      // 4 [9 4] 9 10 11 (12 13 14 15)
    short d;    // 2 [16 2] (16 17)
} strA;
struct StructB {
    double a;   //8 (0-7)
    int b;      //4 (8 9 10 11)
    char c;     //1 (12)
    short d;    //2 13 (14 15) - 16
} strB;

// 輸出
NSLog(@"strA = %lu,strB = %lu", sizeof(strA), sizeof(strB));
strA = 24,strB = 16
  • 由下圖,可以看到StructA&StructB內存對齊情況

詳解:

strutA:

  • double a:佔8個字節,即從0-7儲存double a
  • char b:佔1個字節,8/1=8...0,即從8儲存char a
  • int c:佔4個字節,9/4=2...1,有餘數就不能整除,繼續往後移動,找到12/4=3...0 ,即從12-15儲存int c
  • short d:佔2個字節,16/2 = 8...0,即從16-17儲存short d
  • 所以StructA需要的大小為15個字節,StructA最大變量的字節數為8,實際內存必須是8的整數倍,17向上取整到24,所以sizeof(StructA) 的结果是 24

strutB:

  • double a:佔8個字節,即從0-7儲存double a
  • int b:佔4個字節,8/4=2...0,即從8-11儲存int b
  • char c:佔1個字節,12/1=0,即從12儲存char c
  • short d:佔2個字節,13/2 = 6...1,即從14-15儲存short d
  • 所以StructB需要的大小為15個字節,StructA最大變量的字節數為8,實際內存必須是8的整數倍,16是8的整數倍,所以sizeof(StructA) 的结果是 16

範例二:

結構體嵌套結構體

struct StructA {
    double a;   // 8字節
    char b;     // 1字節 
    int c;      // 4字節 
    short d;    // 2字節 
} strA;
struct StructB {
    double a;   //8字節 
    int b;      //4字節 
    char c;     //1字節
    short d;    //2字節 
} strB;
struct StructC {
   double a;   //8字節
   int b;      //4字節
   char c;     //1字節
   short d;    //2字節
struct StructB str;    //16字節
} strC;

// 輸出
NSLog(@"strA = %lu,strB = %lu,strC = %lu", sizeof(strA), sizeof(strB), sizeof(strC));
strA = 24,strB = 16,strC = 32

詳解

structC

  • double a:佔8個字節,即從0-7儲存double a
  • int b:佔4個字節,8/4=2...0,即從8-11儲存int b
  • char c:佔1個字節,12/1=0,即從12儲存char c
  • short d:佔2個字節,13/2 = 6...1,即從14-15儲存short d
  • 結構體B結構體B最大的成员大小為8,所以str要從8的整數倍開始,當前是從16開始,,16是8的整數倍,符合內存對齊原則,所以16-31 存储 結構體B

內存優化(屬性重排)

透過剛剛範例,我們可以得出一個結論,結構體內存大小與結構體結構體成員內存大小的順序有關

下面來了一個例子說明蘋果屬性重排,即內存優化

  • 定義一個類,並定義一些屬性
@interface LGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;

@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end
int main(int argc, char * argv[]) {
    @autoreleasepool {
       LGPerson *p1 = [LGPerson alloc];
        p1.name      = @"Cooci";
        p1.nickName  = @"帥哥";
        p1.age       = 18;
        p1.c1        = 'a';
        p1.c2        = 'b';

        LGPerson *p2 = [p1 init];
        p2.name      = @"C.C";
        p2.nickName  = @"美女";
        p2.age       = 18;
        p2.c1        = 'a';
        p2.c2        = 'b';
    }
    return 0;
}
  • 斷點調試,根據LGPerson的對象地址,查找出屬性的值。
  • 通過地址找出 name & nickName

注意:可以看到原本把Cocci賦值給name ,給改成了C.C,因為這邊只創建了一個對象。

alloc&init原理

  • 當我們向通過0x0000001200006261地址找出age等數據時,發現是亂碼,這裡無法找出值的原因是苹果中针对age、c1、c2属性的内存进行了重排,因為age類型占4個字節,c1和c2類型char分別佔1個字節,通過4+1+1的方式,按照8字节对齐,不足補齊的方式存储在同一块内存中,
    • age的讀取通過0x00000012
    • c1的讀取通過0x62 (98為ASCII編碼)
    • c2的讀取通過0x61(97為ASCII編碼)
  • 以下為p1對象在堆區中的排列情況

總結-內存對齊:

  • 內存是通過固定的內存塊進行讀取
  • 蘋果會自動對屬性依據原則進行重排,進行優化内存

你可能感兴趣的:(內存對齊原理)