內存管理系列—從C語言看OC的內存佈局

內存管理系列文章:ios

前言

OC底層實現其實都是C語言的代碼,因此想深刻理解iOS的內存管理機制,能夠經過了解C語言的內存管理來進一步熟悉OC的內存管理。程序員

內存分類

  • RAM:運行內存,不能掉電儲存;
  • ROM:儲存性內存,能夠掉電儲存,例如:內存卡,flash;

RAM和ROM的區別bash

  1. RAM的訪問速度要遠高於ROM,價格也要高;
  2. CPU只能從RAM直接讀取指令;
  3. app程序通常存放於ROM中。啓動app時,系統會把開啓的app程序從ROM中轉移到RAM中

從內存分配看app加載過程

在瞭解OC的內存分配以前,先看下app的加載過程:數據結構

  1. 當APP沒有打開時,ipa或者app文件都是存在於ROM中,即咱們說一個叫Application的文件夾中。
  2. 在APP啓動的時候iOS系統會先爲app從RAM中分配一個獨立的內存空間(即沙盒),app全部的內存操做都在這個獨立的沙盒中進行。
  3. 接着系統會先加載二進制代碼到內存中,而後加載常量區中的常量,接着加載全局區和靜態區(初始化過的靜態區和沒有初始化過的靜態區是分開的)
  4. 以後程序會找main入口函數開始執行代碼,在執行代碼的過程當中,會建立對象和一些局部變量,其中對象存放在堆中,變量存放在棧上。

內存分區

畫圖工具沒找到太合適的,粗略的作了一張圖,經過如下這張圖對內存佈局有個大概認識:app

區段解釋

  1. 程序代碼區:代碼區用來存放函數體的二進制代碼,程序結束後由系統釋放函數

  2. 常量區: 常量區用來存放常量字符串等,程序結束後由系統釋放工具

  3. 靜態區:全局變量和靜態變量的存儲是放在一塊的,程序結束後由系統釋放佈局

  • 初始化的全局變量和靜態變量在一塊區域,.data。
  • 未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域,.bSS。

    全局變量和靜態變量要儘可能少用。由於這些變量在程序的生命週期中不會變釋放,比較容易佔用內存空間,不適合存儲比較大量的數據post

  1. 堆區(heap)
  • 堆區的地址是從低到高分配,因此先聲明的變量地址要比後聲明的變量地址小
  • 通常由程序員分(new、malloc)釋放,若程序員不釋放,程序結束時可能由操做系統回收
  • 堆是向高地址擴展的數據結構,,是一塊不連續的內存的區域。引文系統是用鏈表來存儲的空閒內存地址的。
  • 在ios中,堆區的內存是應用程序共享的,堆中的內存分配是系統負責
  1. 棧區(stack)
  • 存放函數的參數值、局部變量的值等,由編譯器自動分配釋放,一般在函數執行結束後就釋放了,iOS中棧區的大小是2M。
  • 棧上的地址是從高到低分配,先聲明的變量地址比後聲明的變量地址要大。
  • 棧是向低地址擴展的數據結構,是一塊連續的內存的區域

堆空間的申請

  1. 操做系統有一個記錄空閒內存地址的鏈表。
  2. 當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。
  3. 因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中

棧空間的申請

每個函數在執行的時候都會向操做系統索要資源,棧區就是函數運行時的內存,棧區中的變量由編譯器負責分配和釋放,內存隨着函數的運行分配,隨着函數的結束而釋放,由系統自動完成。測試

  • 靜態分配是編譯器完成的,好比自動變量(auto)的分配。
  • 動態分配由alloca函數完成。棧的動態分配無需釋放(是自動的),也就沒有釋放函數

堆棧的區別

  • 按管理方式分
  1. 對於棧來說,是由系統編譯器自動管理,不須要程序員手動管理
  2. 對於堆來說,釋放工做由程序員手動管理,不及時回收容易產生內存泄露
  • 按分配方式分
  1. 堆是動態分配和回收內存的,沒有靜態分配的堆
  2. 棧有兩種分配方式:靜態分配和動態分配
    • 靜態分配是系統編譯器完成的,好比局部變量的分配
    • 動態分配是有alloc函數進行分配的,可是棧的動態分配和堆是不一樣的,它的動態分配也由系統編譯器進行釋放,不須要程序員手動管理

小結

我的以爲主要弄清楚如下幾點

  1. 內存分配的地址從低到高,仍是從高到低
  2. 每一個段或者區存放的變量是什麼類型的
  3. 棧和堆的管理、申請、釋放、以及二者區別等,棧和堆是一個重點

demo示例【很重要】

運行環境:選擇的模擬器是Iphone11 Pro Max

static int a = 10;
static int b;

- (void)viewDidLoad {
    [super viewDidLoad];
       /* 代碼段 */
    IMP imp = method_getImplementation(class_getInstanceMethod(self.class, @selector(viewDidLoad)));
    NSLog(@"【代碼段】==> 編譯以後的函數");
    NSLog(@"imp --- %p", imp);

    /* 數據段 */
    static int c = 10;
    static int d;
     /* 常量區 */
    NSString *str1 = @"腳底按摩"; // 直接寫出來的,不是經過方法建立的字符串,編譯時會生成爲【字符串常量】
    NSString *str2 = @"精油推背";
    NSLog(@"【數據段/常量區】==> 字符串常量");
    NSLog(@"str1 --- %p", str1);
    NSLog(@"str2 --- %p", str2);
    /* 靜態初始化區 */
    NSLog(@"【數據段/靜態區】==> 已初始化數據");
    NSLog(@"c ------ %p", &c);
    NSLog(@"a ------ %p", &a);
     /* 靜態未初始化區 */
    NSLog(@"【數據段/靜態區】==> 未初始化數據");
    NSLog(@"d ------ %p", &d);
    NSLog(@"b ------ %p", &b);
    /* 堆 */
    NSObject *obj = [[NSObject alloc] init];
    NSString *str3 = [NSString stringWithFormat:@"%@", @"測試字符串是否在堆上"];
    NSLog(@"【堆】==> 實例對象"); // 分配的內存空間地址【愈來愈大】,不連續的
    NSLog(@"obj ---- %p", obj);
    NSLog(@"str3 --- %p", str3);
    /* 棧 */
    int e = 20;
    int f;
    NSLog(@"【棧】==> 局部變量"); // 分配的內存空間地址【愈來愈小】,是連續的,無論有沒有初始化都會分配
    NSLog(@"e ------ %p", &e);
    NSLog(@"f ------ %p", &f);
}
// 打印結果
2020-03-24 23:05:05.707713+0800 03-內存管理-內存佈局[3812:249391] 【代碼段】==> 編譯以後的函數
2020-03-24 23:05:05.707854+0800 03-內存管理-內存佈局[3812:249391] imp --- 0x10f55cae0
2020-03-24 23:05:05.707985+0800 03-內存管理-內存佈局[3812:249391] 【數據段/常量區】==> 字符串常量
2020-03-24 23:05:05.708091+0800 03-內存管理-內存佈局[3812:249391] str1 --- 0x10f55f060
2020-03-24 23:05:05.708197+0800 03-內存管理-內存佈局[3812:249391] str2 --- 0x10f55f080
2020-03-24 23:05:05.708307+0800 03-內存管理-內存佈局[3812:249391] 【數據段/靜態區】==> 已初始化數據
2020-03-24 23:05:05.708411+0800 03-內存管理-內存佈局[3812:249391] c ------ 0x10f5614c0
2020-03-24 23:05:05.708510+0800 03-內存管理-內存佈局[3812:249391] a ------ 0x10f5614c4
2020-03-24 23:05:05.708621+0800 03-內存管理-內存佈局[3812:249391] 【數據段/靜態區】==> 未初始化數據
2020-03-24 23:05:05.708723+0800 03-內存管理-內存佈局[3812:249391] d ------ 0x10f561648
2020-03-24 23:05:05.708982+0800 03-內存管理-內存佈局[3812:249391] b ------ 0x10f56164c
2020-03-24 23:05:05.719411+0800 03-內存管理-內存佈局[3812:249391] 【堆】==> 實例對象
2020-03-24 23:05:05.719575+0800 03-內存管理-內存佈局[3812:249391] obj ---- 0x600002a05610
2020-03-24 23:05:05.719692+0800 03-內存管理-內存佈局[3812:249391] str3 --- 0x600002642400
2020-03-24 23:05:05.719810+0800 03-內存管理-內存佈局[3812:249391] 【棧】==> 局部變量
2020-03-24 23:05:05.719932+0800 03-內存管理-內存佈局[3812:249391] e ------ 0x7ffee06a10c4
2020-03-24 23:05:05.720063+0800 03-內存管理-內存佈局[3812:249391] f ------ 0x7ffee06a10c0
複製代碼

總結

  1. 棧的地址通常是0x7開始,堆是0x6開始,而數據段是0x1
  2. 靜態區是在同一段內存連續分配的,按內存地址增加方向分配。
  3. 未初始化靜態區的地址比初始化的靜態區地址更大,
  4. 棧空間也是同一段內存連續分配的,按內存地址減少方向分配

問題和探討

  • ❓❓❓堆區的地址是從低到高分配,先聲明的變量地址要比後聲明的變量地址小,可是demo的輸出結果,倒是從高到低分配的,這個還有待進一步瞭解
NSObject *obj = [[NSObject alloc] init];
NSString *str3 = [NSString stringWithFormat:@"%@", @"測試字符串是否在堆上"];
2020-03-24 23:05:05.719411+0800 03-內存管理-內存佈局[3812:249391] 【堆】==> 實例對象
2020-03-24 23:05:05.719575+0800 03-內存管理-內存佈局[3812:249391] obj ---- 0x600002a05610
2020-03-24 23:05:05.719692+0800 03-內存管理-內存佈局[3812:249391] str3 --- 0x600002642400
複製代碼
相關文章
相關標籤/搜索