一、內存基本構成
可編程內存在基本上分為這樣的幾大部分:靜態存儲區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。
堆區
:亦稱動態內存分配
。程序在運行的時候用malloc
或new
申請任意大小的內存,程序員
自己負責在適當的時候用free
或delete
釋放內存。動態內存的生存期可以由我們決定,如果某動態內存不再使用,需要將其釋放掉,否則就會發生內存泄漏
現象。
(OC中對象
存儲于堆中,當對象的應用計數為0時自動釋放該對象)棧區
:在執行函數時,函數內局部變量
的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高
,但是分配的內存容量
有限。
(OC中非對象
的變量
都存在棧中)靜態存儲區
:內存在程序編譯
的時候就已經分配好,這塊內存在程序的整個運行期間
都存在。它主要存放靜態數據
、全局數據
和常量
。
二、使用時需注意的規則
-
【規則1】
用malloc 或new申請內存
之后,應該立即檢查指針
值是否為NULL
。防止使用指針值為NULL的內存。 -
【規則2】
不要忘記為數組
和動態內存
賦初值。防止將未被初始化
的內存作為右值
使用。 -
【規則3】
避免數組
或指針
的下標越界
,特別要當心發生“多1”或者“少1”操作。 -
【規則4】
動態內存的申請與釋放
必須配對,防止內存泄漏
。 -
【規則5】
用free 或delete 釋放了內存之后,立即將指針
設置為NULL
,防止產生野指針
。
三、堆與棧的討論
1、管理方式:
- 堆中資源由程序員控制(容易產生
memory leak
), - 棧中資源由
編譯器自動管理
,無需手工控制。
2、系統響應:
- 對于堆,應知道系統有一個記錄空閑
內存地址
的鏈表,當系統收到程序申請
時,遍歷該鏈表
,尋找第一個空間大于
申請空間的堆結點
,刪除空閑
結點鏈表中的該結點,并將該結點空間分配給程序(大多數系統會在這塊內存空間首地址記錄本次分配的大小,這樣delete才能正確釋放本內存空間,另外系統會將多余的部分重新放入空閑鏈表中)。 - 對于棧,只要棧的
剩余空間大于所申請空間
,系統為程序提供內存,否則報異常提示棧溢出。
3、空間大小:
- 堆是
不連續
的內存區域(因為系統是用鏈表來存儲空閑內存地址),堆大小受限于計算機系統中有效
的虛擬內存
(32bit系統理論上是4G),所以堆的空間比較靈活,比較大。 - 棧是一塊
連續
的內存區域,大小是操作系統預定好的,windows下棧大小是2M(也有是1M,在編譯時
確定,VC中可設置)。
4、碎片問題:
- 對于堆,頻繁的
new/delete
會造成大量碎片
,使程序效率降低。 - 對于棧,它是一個
先進后出
的隊列,進出一一對應
,不會產生碎片。
5、生長方向:
- 堆向上,向
高地址
方向增長。 - 棧向下,向
低地址
方向增長。
6、分配方式:
- 堆都是
動態分配
(沒有靜態分配的堆)。 - 棧有
靜態分配
和動態分配
,
靜態分配由編譯器完成(如局部變量分配),
動態分配由alloca函數
分配,但棧的動態分配的資源由編譯器進行釋放
,無需程序員實現。
7、分配效率:
- 堆由
C/C++函數庫
提供,機制很復雜
。所以堆的效率
比棧低
很多。 - 棧是基于系統提供的
數據結構
,計算機在底層對棧提供支持,分配專門寄存器
存放棧地址,棧操作有專門指令
四、為什么要把堆和棧區分出來
1、從軟件設計
的角度看,棧代表了處理邏輯
,而堆代表了數據
。這種隔離、模塊化的思想,使得處理邏輯更為清晰
2、使得堆
中的內容可以被多個棧
共享(也可以理解為多個線程
訪問同一個對象
)
- 一方面這種
共享
提供了一種有效的數據交互方式(如:共享內存) - 另一方面,堆中的共享常量和緩存可以被所有棧訪問,節省了空間
3、使得動態增長
成為可能,相應棧中只需記錄堆中的一個地址即可
棧因為運行時的需要,比如保存系統運行的上下文,需要進行地址段的劃分。由于棧只能向上增長,因此就會限制住棧存儲內容的能力。而堆不同,堆中的對象是可以根據需要動態增長的
4、面向對象
就是堆和棧的完美結合。
對象的屬性
其實就是數據
,存放在堆
中;而對象的行為(方法)
,就是運行邏輯
,放在棧
中。我們在編寫對象的時候,其實即編寫了數據結構,也編寫的處理數據的邏輯