1.c語言中的記憶體機制
在c語言中,記憶體主要分為如下5個儲存區:
(1)棧(stack):位於函式內的區域性變數(包括函式實參),由編譯器負責分配釋放,函式結束,棧變數失效。
(2)堆(heap):由程式設計師用malloc/calloc/realloc分配,free釋放。如果程式設計師忘記free了,則會造成記憶體洩露,程式結束時該片記憶體會由os**,但程式只要不結束,就有可能造成記憶體洩露。
(3)全域性區/靜態區(global static area): 全域性變數和靜態變數存放區,程式一經編譯好,該區域便存在。並且在c語言中初始化的全域性變數和靜態變數和未初始化的放在相鄰的兩個區域(在c++中,由於全域性變數和靜態變數編譯器會給這些變數自動初始化賦值,所以沒有區分了)。由於全域性變數一直佔據記憶體空間且不易維護,推薦少用。程式結束時釋放。
(4)c風格字串常量儲存區: 專門存放字串常量的地方,程式結束時釋放。
(5)程式**區:存放程式二進位制**的區域。
2.c++語言中的記憶體機制
在c++語言中,與c類似,不過也有所不同,記憶體主要分為如下5個儲存區:
(1)棧(stack):位於函式內的區域性變數(包括函式實參),由編譯器負責分配釋放,函式結束,棧變數失效。
(2)堆(heap):這裡與c不同的是,該堆是由new申請的記憶體,由delete或delete負責釋放。
(3)自由儲存區(free storage):由程式設計師用malloc/calloc/realloc分配,free釋放。如果程式設計師忘記free了,則會造成記憶體洩露,程式結束時該片記憶體會由os**。
(4)全域性區/靜態區(global static area): 全域性變數和靜態變數存放區,程式一經編譯好,該區域便存在。在c++中,由於全域性變數和靜態變數編譯器會給這些變數自動初始化賦值,所以沒有區分了初始化變數和未初始化變數了。需要說明一點,全域性靜態變數和區域性靜態變數都是儲存在同乙個靜態區(全域性區),只是作用域不同。
(5)常量儲存區: 這是一塊比較特殊的儲存區,專門儲存不能修改的常量(一般是const修飾的變數,或是一些常量字串)。
3.堆和棧的區別
3.1 棧
具體的講,現代計算機(馮諾依曼序列執行機制),都直接在**低層支援棧的資料結構。這體現在,有專門的暫存器指向棧所在的位址(ss,堆疊段暫存器,存放堆疊段位址);有專門的機器指令完成資料入棧出棧的操作(彙編中有push和pop指令)。
這種機制的特點是效率高,但支援資料的資料有限,一般是整數、指標、浮點數等系統直接支援的資料型別,並不直接支援其他的資料結構(可以自定義棧結構支援多種資料型別)。因為棧的這種特點,對棧的使用在程式中非常頻繁的 。對子程式的呼叫就是直接利用棧完成的。機器的call指令裡隱含了把返回位址入棧,然後跳轉至子程式位址的操作,而子程式的ret指令則隱含從堆疊中彈出返回位址並跳轉之的操作。
c/c++中的函式自動變數就是直接使用棧的例子,這也就是為什麼當函式返回時,該函式的自動變數自動失效的原因,因而要避免返回棧記憶體和棧引用,以免記憶體洩露。
3.2 堆
和棧不同的是,堆得資料結構並不是由系統(無論是機器硬體系統還是作業系統)支援的,而是由函式庫提供的。基本malloc/calloc/realloc/free函式維護了一套內部的堆資料結構(在c++中則增加了new/delete維護)。
當程式用這些函式去獲得新的記憶體空間時,這套函式首先試圖從內部堆中尋找可用的記憶體空間(常見記憶體分配演算法有:首次適應演算法、迴圈首次適應演算法、最佳適應演算法和最差適應演算法等)。如果沒有可用的記憶體空間,則試圖利用系統呼叫來動態增加程式資料段的記憶體大小,新分配得到的空間首先被組織進內部堆中去,然後再以適當的形式返回給呼叫者。當程式釋放分配的記憶體空間時,這片記憶體空間被返回到內部堆結構中,可能會被適當的處理(比如空閒空間合併成更大的空閒空間),以更適合下一次記憶體分配申請。 這套複雜的分配機制實際上相當於乙個記憶體分配的緩衝池(cache),使用這套機制有如下幾個原因:
(1)系統呼叫可能不支援任意大小的記憶體分配。有些系統的系統呼叫只支援固定大小及其倍數的記憶體請求(按頁分配);這樣的話對於大量的小記憶體分配來說會造成浪費。
(2)系統呼叫申請記憶體可能是代價昂貴的。 系統呼叫可能涉及到使用者態和核心態的轉換。
(3)沒有管理的記憶體分配在大量複雜記憶體的分配釋放操作下很容易造成記憶體碎片。
3.3 棧和堆的對比
從以上介紹中,它們有如下區別:
(1)棧是系統提供的功能,特點是快速高效,缺點是由限制,資料不靈活;而堆是函式庫提供的功能,特點是靈活方便,資料適應面廣,但是效率有一定降低。
(2)棧是系統資料結構,對於程序/執行緒是唯一的;堆是函式庫內部資料結構,不一定唯一。不同堆分配的記憶體無法互相操作。
(3)棧空間分靜態分配和動態分配,一般由編譯器完成靜態分配,自動釋放,棧的動態分配是不被鼓勵的;堆得分配總是動態的,雖然程式結束時所有的資料空間都會被釋放回系統,但是精確的申請記憶體/釋放記憶體匹配是良好程式的基本要素。
(4)碎片問題:對於堆來講,頻繁的new/delete等操作勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式的效率降低;對於棧來講,則不會存在這個問題,因為棧是後進先出(lifo)的佇列。
(5)生長方向:堆的生長方向是向上的,也就是向這記憶體位址增加的方向;對於棧來講,生長方向卻是向下的,是向著記憶體位址減少的方向增長。
(6)分配方式:堆都是動態分配的,沒有靜態分配的堆;棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配則由alloca函式進行分配,但是棧的動態分配和堆不同,它的動態分配是由編譯器進行釋放,無需我們手工實現。
(7)分配效率:棧是機器系統提供的資料結構,計算機在底層提供支援,分配有專門的堆疊段暫存器,入棧出棧有專門的機器指令,這些都決定了棧的高效率執行。而堆是由c/c++函式庫提供的,機制比較複雜,有不同的分配演算法,易產生記憶體碎片,需要對記憶體進行各種管理,效率比棧要低很多。
4.例項分析
例項1:
看下面的一小段c程式,仔細體會各種記憶體分配機制。
int a = 0; //全域性初始化區
char *p1; //全域性未初始化區(c++中則初始化為null)
int main()
例項2看下面的一小段**,體會堆與棧的區別:
int foo()
其中的語句int *p = new int[5];就包含了堆與棧。其中new關鍵字分配了一塊堆記憶體,而指標p本身所佔得記憶體為棧記憶體(一般4個位元組表示位址)。這句話的意思是在棧記憶體中存放了乙個指向一塊堆記憶體的指標p。在程式中先確定在堆中分配記憶體的大小,然後呼叫new關鍵字分配記憶體,最後返回這塊記憶體首址,放入棧中。這段**在vc6下的彙編**為:
00401028 push 14h
0040102a call operator new (00401060)
0040102f add esp,4
00401032 mov dword ptr [ebp-8],eax
00401035 mov eax,dword ptr [ebp-8]
00401038 mov dword ptr [ebp-4],eax
如果需要釋放記憶體,這裡我們需要使用delete p,告訴編譯器,我要刪除的是乙個陣列。
例項3看下面的一小段**,試著找出其中的錯誤:
#include
using namespace std;
int main()
發現問題了嗎?是的,字元陣列a的容量是6個字元,其內容為"hello\0"。a的內容時可以改變的,比如a[0]='x',因為其是在棧上分配的,也就是在執行時確定的內容。但是指標p指向的字串"world"分配在字串常量儲存區,內容為"world\0",常量字串的內容時不可以修改的。從語法上來說,編譯器並不覺得語句p[0]='x'有什麼問題,但是在執行時則會出現"access violation"非法記憶體訪問的問題。
C C 中的記憶體分配機制
本文是總和了其它的幾篇帖子,主要是 一 字串 乙個字元乙個位元組,加上最後的乙個結束符 0 其中,strlen函式返回的字串長度不包括 0 sizeof操作符返回的位元組長度是包括 0 的。二 結構體的儲存 typedef struct sample sizeof sample 為1個位元組 typ...
memcached記憶體分配機制
memcached slab allocator分配機制 slab allocator的基本原理是按照預先規定的大小,將分配的記憶體分割成特定長度的塊,以完全解決記憶體碎片問題。slab allocation的原理相當簡單,就是將分配的記憶體分割成各種尺寸的塊 chunk 並把尺寸相同的塊分成組 c...
Memcache記憶體分配機制
1.page 頁 為記憶體分配的最小單位 memcached 的記憶體分配以page為單位,預設情況下乙個page是1m,可以通過 i引數在啟動時指定。如果需要申請記憶體時,memcached會劃分出乙個新的page並分配給需要的slab區域。page一旦被分配在重啟前不會被 或者重新分配 2.sl...