在iOS中,內(nèi)存主要分為棧區(qū)、堆區(qū)、全局區(qū)、常量區(qū)、代碼區(qū)五大區(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其他線程是
512KBMAC只有
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語言中使用malloc、calloc、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的棧幀布局方式

其中
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ū)中棧幀的變化如下圖所示
