iOS底層原理:內(nèi)存五大區(qū)

在iOS中,內(nèi)存主要分為棧區(qū)、堆區(qū)、全局區(qū)、常量區(qū)、代碼區(qū)五大區(qū)域。如下圖所示

內(nèi)存五大區(qū)域

棧區(qū)(Stack)

定義
  • 棧是系統(tǒng)數(shù)據(jù)結(jié)構(gòu),其對應的進程或者線程是唯一

  • 棧是由高地址向低地址擴展的數(shù)據(jù)結(jié)構(gòu)

  • 棧是一塊連續(xù)的內(nèi)存區(qū)域,遵循先進后出(FILO)原則

  • 棧的地址空間在iOS中是以0x7開頭

  • 棧區(qū)的內(nèi)存一般在運行時分配

存儲

棧區(qū)是由編譯器自動分配并釋放的,主要用來存儲

  • 局部變量

  • 函數(shù)的參數(shù),例如函數(shù)的隱藏參數(shù)(id self,SEL _cmd)

優(yōu)缺點
  • 優(yōu)點:因為棧是由編譯器自動分配并釋放的,不會產(chǎn)生內(nèi)存碎片,所以快速高效

  • 缺點:棧的內(nèi)存大小有限制,數(shù)據(jù)不靈活

    • iOS主線程棧大小是1MB

    • 其他線程是512KB

    • MAC只有8M

以上內(nèi)存大小的說明,在Threading Programming Guide中有相關說明

官方文檔說明

堆區(qū)(Heap)

定義
  • 堆是由低地址向高地址擴展的數(shù)據(jù)結(jié)構(gòu)

  • 堆是不連續(xù)的內(nèi)存區(qū)域,類似于鏈表結(jié)構(gòu)(便于增刪,不便于查詢),遵循先進先出(FIFO)原則

  • 堆的地址空間在iOS中是以0x6開頭,其空間的分配總是動態(tài)

  • 堆區(qū)內(nèi)存的分配一般是在運行時分配

存儲

堆區(qū)是由程序員動態(tài)分配和釋放的,如果程序員不釋放,程序結(jié)束后,可能由操作系統(tǒng)回收,主要用于存放

  • OC中使用alloc或者 使用new開辟空間創(chuàng)建對象

  • C語言中使用malloccalloc、realloc分配的空間,需要free釋放

優(yōu)缺點
  • 優(yōu)點:靈活方便,數(shù)據(jù)適應面廣泛

  • 缺點:需手動管理,速度慢、容易產(chǎn)生內(nèi)存碎片

當需要訪問堆中內(nèi)存時,一般需要先通過對象讀取到棧區(qū)的指針地址,然后通過指針地址訪問堆區(qū)

全局區(qū)(靜態(tài)區(qū),即.bss & .data)

全局區(qū)是編譯時分配的內(nèi)存空間,在iOS中一般以0x1開頭,在程序運行過程中,此內(nèi)存中的數(shù)據(jù)一直存在,程序結(jié)束后由系統(tǒng)釋放,主要存放

  • 未初始化全局變量靜態(tài)變量,即BSS區(qū)(.bss)

  • 已初始化全局變量靜態(tài)變量,即數(shù)據(jù)區(qū)(.data)

其中,全局變量是指變量值可以在運行時被動態(tài)修改,而靜態(tài)變量是static修飾的變量,包含靜態(tài)局部變量和靜態(tài)全局變量

常量區(qū)(即.rodata)

常量區(qū)是編譯時分配的內(nèi)存空間,在程序結(jié)束后由系統(tǒng)釋放,主要存放

  • 已經(jīng)使用了的,且沒有指向的字符串常量

字符串常量因為可能在程序中被多次使用,所以在程序運行之前就會提前分配內(nèi)存

代碼區(qū)(即.text)

代碼區(qū)是編譯時分配主要用于存放程序運行時的代碼,代碼會被編譯成二進制存進內(nèi)存

驗證內(nèi)存五大區(qū)域

運行下面一段代碼,看看變量在內(nèi)存中是如何分配的

- (void)test{
    
    NSInteger i = 123;
    NSLog(@"i的內(nèi)存地址:%p", &i);
    
    NSString *string = @"CJL";
    NSLog(@"string的內(nèi)存地址:%p", string);
    NSLog(@"&string的內(nèi)存地址:%p", &string);
    
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"obj的內(nèi)存地址:%p", obj);
    NSLog(@"&obj的內(nèi)存地址:%p", &obj);  
}

打印結(jié)果如下所示

2020-11-03 21:48:46.846481+0800 demo[3786:131132] i的內(nèi)存地址:0x7ffeefbff498
2020-11-03 21:48:46.846554+0800 demo[3786:131132] string的內(nèi)存地址:0x100004050
2020-11-03 21:48:46.846600+0800 demo[3786:131132] &string的內(nèi)存地址:0x7ffeefbff490
2020-11-03 21:48:46.846648+0800 demo[3786:131132] obj的內(nèi)存地址:0x100434b00
2020-11-03 21:48:46.846689+0800 demo[3786:131132] &obj的內(nèi)存地址:0x7ffeefbff488
  • 對于局部變量i,從地址可以看出是0x7開頭,所以i存放在棧區(qū)

  • 對于字符串對象string,分別打印了string的對象地址string對象的指針地址

    • string的對象地址以0x1開頭,說明是存放在常量區(qū)

    • string對象的指針地址以0x7開頭,說明是存放在棧區(qū)

  • 對于alloc創(chuàng)建的對象obj,分別打印了obj的對象地址obj對象的指針地址

    • obj的對象地址以0x6開頭,說明是存放在堆區(qū)

    • obj對象的指針地址以0x7開頭,說明是存放在棧區(qū)

函數(shù)棧

  • 函數(shù)棧又稱為棧區(qū),在內(nèi)存中從高地址往低地址分配,與堆區(qū)相對,具體圖示請查看文章最開始的圖示

  • 棧幀是指函數(shù)(運行中且未完成)占用的一塊獨立的連續(xù)內(nèi)存區(qū)域

  • 應用中新創(chuàng)建的每個線程都有專用的棧空間,棧可以在線程期間自由使用。而線程中有千千萬萬的函數(shù)調(diào)用,這些函數(shù)共享進程的這個??臻g每個函數(shù)所使用的??臻g是一個棧幀,所有的棧幀就組成了這個線程完整的棧

  • 函數(shù)調(diào)用是發(fā)生在棧上的,每個函數(shù)的相關信息(例如局部變量、調(diào)用記錄等)都存儲在一個棧幀中,每執(zhí)行一次函數(shù)調(diào)用,就會生成一個與其相關的棧幀,然后將其棧幀壓入函數(shù)棧,而當函數(shù)執(zhí)行結(jié)束,則將此函數(shù)對應的棧幀出棧并釋放掉

如下圖所示,是經(jīng)典圖 - ARM的棧幀布局方式

ARM的棧幀布局方式
  • 其中main stack frame調(diào)用函數(shù)的棧幀

  • func1 stack frame當前函數(shù)(被調(diào)用者)的棧幀

  • 棧底地址,棧向下增長。

  • FP就是?;?/code>,它指向函數(shù)的棧幀起始地址

  • SP則是函數(shù)的棧指針,它指向棧頂?shù)奈恢?/code>。

  • ARM壓棧順序很是規(guī)矩(也比較容易被黑客攻破么),依次為當前函數(shù)指針PC返回指針LR、棧指針SP、棧基址FP、傳入?yún)?shù)個數(shù)及指針、本地變量臨時變量。如果函數(shù)準備調(diào)用另一個函數(shù),跳轉(zhuǎn)之前臨時變量區(qū)先要保存另一個函數(shù)的參數(shù)。

  • ARM也可以用棧基址和棧指針明確標示棧幀的位置,棧指針SP一直移動,ARM的特點是,兩個??臻g內(nèi)的地址(SP+FP)前面,必然有兩個代碼地址(PC+LR)明確標示著調(diào)用函數(shù)位置內(nèi)的某個地址。

堆棧溢出

一般情況下應用程序是不需要考慮堆和棧的大小的,但是事實上堆和棧都不是無上限的,過多的遞歸會導致棧溢出,過多的alloc變量會導致堆溢出。

所以預防堆棧溢出的方法:

  • 避免層次過深遞歸調(diào)用

  • 不要使用過多的局部變量,控制局部變量的大小

  • 避免分配占用空間太大的對象,并及時釋放

  • 實在不行,適當?shù)那榫跋?code>調(diào)用系統(tǒng)API修改線程的堆棧大小

棧幀示例

棧幀程序示例

int Add(int x,int y) {
    int z = 0;
    z = x + y;
    return z;
}

int main() {
    int a = 10;
    int b = 20;
    int ret = Add(a, b);
}

程序執(zhí)行時棧區(qū)中棧幀的變化如下圖所示

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

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