C/C++內存分配與內存分區

轉載來源1:http://www.cnblogs.com/particle/archive/2012/09/01/2667034.html
轉載來源2:http://blog.csdn.net/k346k346/article/details/45592329

內存分配函數:malloc,calloc,realloc,alloca

extern void *malloc(unsigned int num_bytes);

第一、malloc 函數返回的是 void * 類型。對於C++,如果你寫成:p = malloc (sizeof(int)); 則程序無法通過編譯,報錯:“不能將 void* 賦值給 int * 類型變量”。所以必須通過 (int *) 來將強制轉換。而對於C,沒有這個要求,但爲了使C程序更方便的移植到C++中來,建議養成強制轉換的習慣。
第二、函數的實參爲 sizeof(int) ,用於指明一個整型數據需要的大小。如果你寫成:
  int* p = (int *) malloc (1);
  代碼也能通過編譯,但事實上只分配了1個字節大小的內存空間,當你往裏頭存入一個整數,就會有3個字節無家可歸,而直接“住進鄰居家”!造成的結果是後面的內存中原有數據內容被改寫。
工作機制:
malloc函數的實質體現在,它有一個將可用的內存塊連接爲一個長長的列表的所謂空閒鏈表。調用malloc函數時,它沿連接表尋找一個大到足以滿足用戶請求所需要的內存塊。然後,將該內存塊一分爲二(一塊的大小與用戶請求的大小相等,另一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳給用戶,並將剩下的那塊(如果有的話)返回到連接表上。調用free函數時,它將用戶釋放的內存塊連接到空閒鏈上。到最後,空閒鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段,那麼空閒鏈上可能沒有可以滿足用戶要求的片段了。於是,malloc函數請求延時,並開始在空閒鏈上翻箱倒櫃地檢查各內存片段,對它們進行整理,將相鄰的小空閒塊合併成較大的內存塊。如果無法獲得符合要求的內存塊,malloc函數會返回NULL指針,因此在調用malloc動態申請內存塊時,一定要進行返回值的判斷。

void *calloc(unsigned n,unsigned size);

 功 能: 在內存的動態存儲區中分配n個長度爲size的連續空間,函數返回一個指向分配起始地址的指針;如果分配不成功,返回NULL。

  跟malloc的區別:calloc在動態分配完內存後,自動初始化該內存空間爲零,而malloc不初始化,裏邊數據是隨機的垃圾數據。

extern void *realloc(void *mem_address, unsigned int newsize);

 功能:先判斷當前的指針是否有足夠的連續空間,如果有,擴大mem_address指向的地址,並且將mem_address返回,如果空間不夠,先按照newsize指定的大小分配空間,將原有數據從頭到尾拷貝到新分配的內存區域,而後釋放原來mem_address所指內存區域,同時返回新分配的內存區域的首地址。即重新分配存儲器塊的地址。
_alloca:

函數原型爲:void * __cdecl alloca(size_t);

內存分配函數,與malloc,calloc,realloc類似.
  但是注意一個重要的區別,_alloca是在棧(stack)上申請空間,用完馬上就釋放.

  在某些系統中會宏定義成alloca使用.
  

C/C++的四大內存分區

導讀

正確的理解C/C++程序的內存分區,是合格程序猿的基本要求。
網絡上流形兩大版本內存分區,分別爲:

  1. 五大內存分區:堆、棧、全局/靜態存儲區、自由存儲區和常量存儲區。
  2. 五大內存分區:堆、棧、全局/靜態存儲區、字符串常量區和代碼區。

且不論以上兩種分區孰是孰非,孰優孰劣,我認爲具體的內存分區和編譯器有很大關係,我想不同編譯器對內存的劃分都不盡相同,但也大同小異。
綜合對比,查閱相關資料,提出自己對C/C++程序的內存分區的認識。可劃分爲四大內存分區:堆、棧、靜態存儲區和代碼區。

堆區
由程序猿手動申請,手動釋放,若不手動釋放,程序結束後由系統回收,生命週期是整個程序運行期間。使用malloc或者new進行堆的申請,堆的總大小爲機器的虛擬內存的大小。
說明:new操作符本質上是使用了malloc進行內存的申請,new和malloc的區別如下:
(1)malloc是C語言中的函數,而new是C++中的操作符。
(2)malloc申請之後返回的類型是void*,而new返回的指針帶有類型。
(3)malloc只負責內存的分配而不會調用類的構造函數,而new不僅會分配內存,而且會自動調用類的構造函數。

棧區:
由系統進行內存的管理。主要存放函數的參數以及局部變量。在函數完成執行,系統自行釋放棧區內存,不需要用戶管理。整個程序的棧區的大小可以在編譯器中由用戶自行設定,VS中默認的棧區大小爲1M,可通過VS手動更改棧的大小。64bits的Linux默認棧大小爲10MB,可通過ulimit -s臨時修改。

靜態儲存區:
靜態存儲區內的變量在程序編譯階段已經分配好內存空間並初始化。這塊內存在程序的整個運行期間都存在,它主要存放靜態變量、全局變量和常量。
注意:
(1)這裏不區分初始化和未初始化的數據區,是因爲靜態存儲區內的變量若不顯示初始化,則編譯器會自動以默認的方式進行初始化,即靜態存儲區內不存在未初始化的變量。
(2)靜態存儲區內的常量分爲常變量和字符串常量,一經初始化,不可修改。靜態存儲內的常變量是全局變量,與局部常變量不同,區別在於局部常變量存放於棧,實際可間接通過指針或者引用進行修改,而全局常變量存放於靜態常量區則不可以間接修改。
(3)字符串常量存儲在靜態存儲區的常量區,字符串常量的名稱即爲它本身,屬於常變量。
(4)數據區的具體劃分,有利於我們對於變量類型的理解。不同類型的變量存放的區域不同。後面將以實例代碼說明這四種數據區中具體對應的變量。

代碼區:
存放程序體的二進制代碼。比如我們寫的函數,都是在代碼區的。

示例代碼:

int a = 0;//靜態全局變量區
char *p1; //編譯器默認初始化爲NULL
void main()
{
    int b; //棧
    char s[] = "abc";//棧
    char *p2 = "123456";//123456在字符串常量區,p2在棧上
    static int c =0; //c在靜態變量區,0爲文字常量,在代碼區
    const int d=0; //棧
    static const int d;//靜態常量區
    p1 = (char *)malloc(10);//分配得來得10字節在堆區。
    strcpy(p1, "123456"); //123456放在字符串常量區,編譯器可能會將它與p2所指向的"123456"優化成一個地方
}

以上所有代碼,編譯成二進制後存放於代碼區,文字常量存放於代碼區,是不可尋址的。

總結

在理解C/C++內存分區時,常會碰到如下術語:數據區,堆,棧,靜態存儲區,靜態區,常量區,常變量區,全局區,字符串常量區,靜態常量區,靜態變量區,文字常量區,代碼區等等,初學者被搞得雲裏霧裏。在這裏,嘗試捋清楚以上分區的關係。
數據區包括:堆,棧,靜態存儲區。
靜態存儲區包括:常量區(靜態常量區),全局區(全局變量區)和靜態變量區(靜態區)。
常量區包括:字符串常量區和常變量區。
代碼區:存放程序編譯後的二進制代碼,不可尋址區。

可以說,C/C++內存分區其實只有兩個,即代碼區和數據區。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章