一、C 程序的內(nèi)存結(jié)構(gòu)
因?yàn)?Objetive-C 是基于 C 之上的,為了能充分了解 OC 中的 Stack 和 Heap, 讓我們先看看在一個(gè) C 程序中,內(nèi)存是如何分布的。
如圖所示筑凫,一個(gè) C 程序的內(nèi)存分布共有5個(gè)區(qū):
code/代碼區(qū): 代碼區(qū)包含程序編譯后的所有方法,包括系統(tǒng)的方法并村。通常以機(jī)器碼的形式儲(chǔ)存巍实。代碼區(qū)通常是只讀的,并且內(nèi)存大小是固定的橘霎。代碼區(qū)處于內(nèi)存的地位蔫浆,以防止 Stack 與 Heap 覆蓋了代碼區(qū)。
initialized data/全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū): 全局初始化數(shù)據(jù)區(qū)可簡(jiǎn)稱為數(shù)據(jù)區(qū)姐叁,數(shù)據(jù)區(qū)只初始化一次瓦盛,用來保存全局與靜態(tài)變量。進(jìn)一步劃分可劃分為 只讀數(shù)據(jù)區(qū) 與 讀寫數(shù)據(jù)區(qū)外潜,例如原环,當(dāng)創(chuàng)建 char s[] = "Hello"; 時(shí),字符串的指針 s 會(huì)儲(chǔ)存于讀寫數(shù)據(jù)區(qū)处窥,而 "Hello" 字符串會(huì)儲(chǔ)存于只讀數(shù)據(jù)區(qū)嘱吗。
uninitialized data(bss)/未初始化數(shù)據(jù)區(qū): 未初始化數(shù)據(jù)區(qū)用來儲(chǔ)存沒有賦值的全局與靜態(tài)變量。例如滔驾,global int j; 與 static int i; 都會(huì)儲(chǔ)存于這個(gè)區(qū)谒麦。
stack/棧: 棧是一片動(dòng)態(tài)的內(nèi)存區(qū)域,從內(nèi)存的高位往地位擴(kuò)張哆致,一般與堆的擴(kuò)張方向相反绕德。類似于棧的數(shù)據(jù)結(jié)構(gòu),具有LIFO的?特性摊阀。每當(dāng)一個(gè)方法被調(diào)用時(shí)耻蛇,一個(gè) stack frame/棧幀 就會(huì)被壓入棧中,棧幀 里有所調(diào)用方法的返回地址胞此、參數(shù)與局部變量等相關(guān)信息臣咖。當(dāng)當(dāng)前方法返回時(shí),相應(yīng)的棧幀會(huì)被銷毀漱牵。棧內(nèi)的內(nèi)存管理都是由系統(tǒng)進(jìn)行調(diào)用和銷毀的夺蛇,所以程序員不需要進(jìn)行操作。
heap/堆: 堆一般處于內(nèi)存的低位往高位擴(kuò)張(與棧的擴(kuò)張方向相反)酣胀。堆是動(dòng)態(tài)內(nèi)存區(qū)蚊惯,內(nèi)存由程序員進(jìn)行分配管理愿卸。開發(fā)時(shí)可使用 malloc(), calloc(), realloc() 進(jìn)行內(nèi)存的分配,并使用 free() 進(jìn)行內(nèi)存的銷毀截型。
Stack 和 Heap 的主要區(qū)別
- 訪問速度:stack 的訪問速度比 heap 快
- 內(nèi)存限制:stack 一般有內(nèi)存大小限制,而heap沒有儒溉,除非是硬件的性能限制
- 作用域:stack 中的變量只會(huì)在方法調(diào)用時(shí)存在宦焦,當(dāng)方法返回時(shí),變量會(huì)被銷毀顿涣。而 heap 中的變量是全局的波闹,能一直訪問,直至該內(nèi)存塊被程序員手動(dòng)銷毀涛碑。
- 內(nèi)存碎片化:Stack 上的內(nèi)存由 CPU 管理精堕,不會(huì)出現(xiàn)內(nèi)存碎片化的情況。而處于 Heap 上的內(nèi)存不能保證能充分利用蒲障,可能會(huì)出現(xiàn)內(nèi)存碎片化
何時(shí)使用 Stack 和 Heap
- 本地變量首選使用 Stack, 因?yàn)樽x寫更快歹篓,而且內(nèi)存由系統(tǒng)自動(dòng)管理。
- 當(dāng)需要使用占用內(nèi)存大的數(shù)據(jù)結(jié)構(gòu)揉阎,使用 Heap庄撮。如 array, set 等, 因?yàn)?Stack 的內(nèi)存大小有限制毙籽。當(dāng)使用的內(nèi)存超出 Stack 可使用范圍內(nèi)洞斯,便會(huì)出現(xiàn) StackOverflow.
- 當(dāng)需要一個(gè)作用域大于當(dāng)前方法作用域的變量時(shí),使用 Heap坑赡。例如烙如,一個(gè)方法需要返回一個(gè)數(shù)組。該數(shù)組必須創(chuàng)建于 Heap毅否,因?yàn)?Stack 內(nèi)的局部變量會(huì)在返回時(shí)被銷毀亚铁,返回 Stack 上的變量會(huì)在編譯時(shí)報(bào)錯(cuò)。
- 當(dāng)一個(gè)變量的內(nèi)存大小不確定時(shí)搀突,使用 Heap刀闷。如 動(dòng)態(tài)數(shù)組。同理仰迁,因?yàn)?stack 上的內(nèi)存限制甸昏,當(dāng)變量的內(nèi)存大小不確定時(shí),最好選用 Heap徐许。
二施蜜、Objective-C 中的 Stack 與 Heap
在 Objective-C 中,NSObject 包含了一個(gè)類指針 Class *isa, 里面指向?qū)ο箢惖膶傩粤斜泶朴纭⒎椒斜淼刃畔⒎靡嬗?Objective-C 強(qiáng)大的 runtime缸沃,運(yùn)行時(shí)可以再對(duì)類添加方法、添加屬性修械。
換句話說趾牧,NSObject 類在編譯時(shí)是無(wú)法確定其占用內(nèi)存大小的,因?yàn)樵谶\(yùn)行時(shí)可以再對(duì)類進(jìn)行修改肯污,所以理所當(dāng)然的翘单,所有繼承于 NSObject 的類,在新建對(duì)象時(shí)都是分配在 Heap 上的蹦渣。而在 Objective-C 中哄芜,所有的類都是繼承于 NSObject,所以我們新建的對(duì)象都處于 Heap 上柬唯,必須使用引用計(jì)數(shù)進(jìn)行內(nèi)存管理认臊。
Block 的內(nèi)存管理
Objetive-C 中 Block 的內(nèi)存管理與其他的類稍有不同,以下是三個(gè)不同類型的 Block 以及他們的使用場(chǎng)景锄奢,根據(jù)不同的使用情況失晴,程序編譯時(shí)會(huì)創(chuàng)建不一樣類型的 Block:(這里討論的都是使用 ARC 時(shí)的情況,使用 MRC 的話會(huì)稍有不同)
- NSConcreateGlobalBlock: 內(nèi)存分配在靜態(tài)數(shù)據(jù)區(qū)斟薇,當(dāng) Block 不需要訪問訪問外部變量時(shí)师坎。
id block = ^(){};
NSLog(@"This is a global block: %@", block);
// This is a global block: <__NSGlobalBlock__: 0x1054480a0>
- NSConcreateStackBlock: 內(nèi)存分配在 Stack,當(dāng) Block 引用外部變量堪滨,而且沒有 owner 時(shí)胯陋,即沒有指針引用當(dāng)前 Block。
int b = 100;
NSLog(@"This is a stack block: %@", ^(){
int a = b;
});
// This is a stack block: : <__NSStackBlock__: 0x7ffeea7b68d8>
- NSConcreateMallocBlock: 內(nèi)存分配在 Heap袱箱,當(dāng) Block 引用外部變量遏乔,并被引用或作為返回值時(shí)
int b = 100;
id block = ^(){
int a = b;
};
NSLog(@"This is a Malloc Block being referenced: %@", block);
// This is a Malloc Block being referenced: <__NSMallocBlock__: 0x600003a193e0>
- (void(^)(void))block
{
int b = 100;
return ^(){
int a = b;
};
}
NSLog(@"This is a Malloc Block being returned: %@", [self block]);
// This is a Malloc Block being returned: <__NSMallocBlock__: 0x600003a19140>
參考資料:
Memory Layout of C Programs
C語(yǔ)言內(nèi)存分配-通俗理解
What's the difference between a stack and a heap?