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

iOS 底層原理 文章匯總

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

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

下面分別介紹這五大區(qū)

棧區(qū)(Stack)

定義

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

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

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

  • 棧的地址空間在iOS中是以0X7開(kāi)頭

  • 棧區(qū)一般在運(yùn)行時(shí)分配

存儲(chǔ)

棧區(qū)是由編譯器自動(dòng)分配并釋放的,主要用來(lái)存儲(chǔ)

  • 局部變量

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

優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):因?yàn)闂J怯?code>編譯器自動(dòng)分配并釋放的,不會(huì)產(chǎn)生內(nèi)存碎片,所以快速高效

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

    • iOS主線程棧大小是1MB

    • 其他線程是512KB

    • MAC只有8M

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

官方文檔說(shuō)明

堆區(qū)(Heap)

定義

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

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

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

  • 堆區(qū)的分配一般是在運(yùn)行時(shí)分配

存儲(chǔ)

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

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

  • C語(yǔ)言中使用malloc、calloc、realloc分配的空間,需要free釋放

優(yōu)缺點(diǎn)

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

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

當(dāng)需要訪問(wèn)堆中內(nèi)存時(shí),一般需要先通過(guò)對(duì)象讀取到棧區(qū)的指針地址,然后通過(guò)指針地址訪問(wèn)堆區(qū)

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

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

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

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

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

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

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

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

字符串常量因?yàn)榭赡茉诔绦蛑斜欢啻问褂?,所以`在程序運(yùn)行之前就會(huì)提前分配內(nèi)存

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

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

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

運(yùn)行下面一段代碼,看看變量在內(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);  
}

運(yùn)行結(jié)果如下


運(yùn)行結(jié)果
  • 對(duì)于局部變量i,從地址可以看出是0x7開(kāi)頭,所以i存放在棧區(qū)
  • 對(duì)于字符串對(duì)象string,分別打印了string的對(duì)象地址string對(duì)象的指針地址
    • string的對(duì)象地址是以0x1開(kāi)頭,說(shuō)明是存放在常量區(qū)

    • string對(duì)象的指針地址是以0x7開(kāi)頭,說(shuō)明是存放在棧區(qū)

  • 對(duì)于alloc創(chuàng)建的對(duì)象obj,分別打印了obj的對(duì)象地址obj對(duì)象的指針地址(可以參考前文的匯總圖)
    • obj的對(duì)象地址是以0x6開(kāi)頭,說(shuō)明是存放在堆區(qū)

    • obj對(duì)象的指針地址是以0x7開(kāi)頭,說(shuō)明是存放在棧區(qū)

函數(shù)棧

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

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

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

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

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

ARM的棧幀布局方式

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

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

  • 棧底地址,棧向下增長(zhǎng)。

  • FP就是棧基址,它指向函數(shù)的棧幀起始地址

  • SP則是函數(shù)的棧指針,它指向棧頂的位置。

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

  • ARM也可以用?;泛蜅V羔樏鞔_標(biāo)示棧幀的位置,棧指針SP一直移動(dòng),ARM的特點(diǎn)是,兩個(gè)??臻g內(nèi)的地址(SP+FP)前面,必然有兩個(gè)代碼地址(PC+LR)明確標(biāo)示著調(diào)用函數(shù)位置內(nèi)的某個(gè)地址。

堆棧溢出

一般情況下應(yīng)用程序是不需要考慮堆和棧的大小的,但是事實(shí)上堆和棧都不是無(wú)上限的,過(guò)多的遞歸會(huì)導(dǎo)致棧溢出,過(guò)多的alloc變量會(huì)導(dǎo)致堆溢出

所以預(yù)防堆棧溢出的方法:
(1)避免層次過(guò)深遞歸調(diào)用;

(2)不要使用過(guò)多的局部變量,控制局部變量的大??;

(3)避免分配占用空間太大的對(duì)象,并及時(shí)釋放;

(4)實(shí)在不行,適當(dāng)?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í)行時(shí)棧區(qū)中棧幀的變化如下圖所示


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

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