内存四区 malloc/free与new/delete的区别
前言
之前寫了一篇關于《快速排序的4種優化》的博文,當時在驗證各種情況的時候忽略內存分配的問題,導致所得到的結果分析的不全面。因為在剛開始寫程序的時候將數組聲明在 main() 里面,這樣數組占用的棧空間,影響了遞歸的深度,也影響了程序處理的數據量(即使不用尾遞歸,處理的數據量也能超過 4 萬)。在了解內存分配問題之前,先復習一下進程的概念。進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位(引入線程后,調度單位為線程)。簡而言之,進程是程序的基本執行實體,是活的程序。程序和進程最大的差別為:是否獲得了系統資源。進程會占用一定數量的內存,它可能是用來存放從磁盤載入的程序代碼,也可能是存放取自用戶輸入的數據等。不過進程對這些內存的管理方式因內存用途不一而不盡相同,有些內存是事先靜態分配和統一回收的,而有些卻是按需要動態分配和回收的。
?
對任何一個普通進程來講,它都會涉及到不同的數據段(如代碼段,數據段,bss 段,堆段,棧段)
代碼段:代碼段是用來存放可執行文件的操作指令,也就是說是它是可執行程序在內存中的鏡像。代碼段需要防止在運行時被非法修改,所以只準許讀取操作。其通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,并且內存區域通常屬于只讀,某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的變量,例如字符串常量等。
初始化數據段:通常將此段稱為數據段,它用來存放可執行文件中需要明確賦初值的變量,換句話說就是存放程序靜態分配的變量和全局變量。例如:int a = 100; 。
未初始化數據段:通常將此段稱為?bss 段,它包含了程序中未初始化的全局變量。bss 是英文 Block Started by Symbol 的簡稱,bss 段屬于靜態內存分配。在程序開始執行之前,內核將此段中的數據初始化為 0 或空指針。例如:int sum[100]; 。// 此時,sum數組內的所有元素都為 0。
堆(heap):堆是用于存放進程運行中被動態分配的內存段,它的大小并不固定,可動態擴張或縮減。當進程調用 malloc() 或 new 分配內存時,新分配的內存就被動態添加到堆上;當利用 free() 或 delete 釋放內存時,被釋放的內存從堆中被剔除。堆區由程序員分配釋放, 若程序員不釋放,程序結束時可能由 OS 回收 。注意:它與數據結構中的堆是兩回事,分配方式類似于鏈表。( C++ Primer Plus 中文版 356 頁說 new 創建的對象將駐留在棧內存是翻譯者的錯。)
棧:棧區(不同于數據結構中的棧)是用戶存放程序臨時創建的局部變量,也就是說我們函數括弧 "{}" 中定義的變量(但不包括 static 聲明的變量,static 意味著在數據段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,并且待到調用結束后,函數的返回值也會被存放回棧中。由于棧的后進先出特點,所以棧特別方便用來保存/恢復調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時數據的內存區。棧區由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。它是由操作系統分配的,內存的申請與回收都由 OS 管理。
內存四區圖:
注意:棧的分配是由高向低,而堆是由低向高。
?
在進程被載入內存中時,基本上被分裂成許多小的節(section)。需要關注的是6個主要的節:
(1) .text 節:基本上相當于二進制可執行文件的 .text 部分,它包含了完成程序任務的機器指令。該節標記為只讀,如果發生寫操作,會造成 segmentation fault。在進程最初被加載到內存中開始,該節的大小就被固定。
(2) .data 節:用來存儲初始化過的變量,如:int a = 0; 該節的大小在運行時固定的。
(3) .bss 節:用來存儲未初始化的變量,如:int a; 該節的大小在運行時固定的。
(4) 堆節(heapsection):用來存儲動態分配的變量,位置從內存的低地址向高地址增長。內存的分配和釋放通過 malloc() 和 free() 函數控制。
(5) 棧節(stacksection):用來跟蹤函數調用(可能是遞歸的),在大多數系統上從內存的高地址向低地址增長。同時,棧這種增長方式,導致了緩沖區溢出的可能性。
(6) 環境/參數節(environment/argumentssection):用來存儲系統環境變量的一份復制文件,進程在運行時可能需要。例如,運行中的進程,可以通過環境變量來訪問路徑、shell 名稱、主機名等信息。該節是可寫的,因此在格式串(format string)和緩沖區溢出(buffer overflow)攻擊中都可以使用該節。另外,命令行參數也保持在該區域中。
之前,我對這類知識不太關注,只是知道個大概,但是,最近的寫的快排優化,讓我意識到這類問題的重要性(也是對知識盲區的學習)。在哪一篇博文中,我習慣性的將數組聲明寫在了 main() 函數里。從而得出了在 Codeblocks 里使用固定基準方式(沒有尾遞歸的情況)處理升序數組時只能處理 4 萬個數組元素的結論(這一結論并不能說錯,但忽略了內存分配的問題)。因為我之前并不太清楚 main 函數中的數組(不是動態分配)占用的是棧的空間。所以這就影響了遞歸的深度。準確的說,要是將數組聲明成全局的,那個算法就可以處理更多的數據元素。上面的概念我們已經學習了,簡化其復雜的內容,可以將內存的分配當做只有 4 個部分(代碼區、數據區、堆和棧)。現在就用一個簡單的例子來看看各種變量在內存中的分配情況:
#include <stdio.h>int a = 0; ???//a在全局已初始化數據區 ? int asd[10]; ?//asd[]bss段 char *p1; ??? //p1在bss段(未初始化全局變量) ? int main() ? {int b; ??????????????? //b在棧區char s[] = "abc"; ???? //s為數組變量,內容存儲在棧區char *p1,p2; ????????? //p1、p2在棧區char *p3 = "123456"; ?? //123456\0是字符串常量,而p3在棧區 ?static int c = 0; ???? //C為靜態數據,存在于已初始化數據區,另外,靜態數據會自動初始化p1 = (char*)malloc(10); //分配得來的10個字節的區域在堆區p2 = (char*)malloc(20); //分配得來的20個字節的區域在堆區free(p1);free(p2);return 0; }?
2019.4.30補充
今天使用了一個函數 char *strtok_r(char *str, const char *delim, char **saveptr);,它的功能是切割字符串。傳參的時候,我習慣性的創建char * str = " abc def "; 然后使用這個函數,結果程序報段錯誤。嘗試修改為char str[ ] = " abc def "; 就可以解決問題。
這是因為這兩種方式的操作對象不同。使用 char * str = "abc def" 后,編譯器在內存的常量區分配一塊內存,保存 "abc def" 這一字符串字面值,然后在棧上分配內存保存 str, str 的內容為 "abc def" 的地址。str 試圖修改常量 "abc def" 的內容時(例如str[0] = 'g'),程序就崩了。而 char str[] = "abc def" 定義了一個數組,編譯器為其在棧上分配了內存空間,因而可以進行修改操作。
#include <stdio.h> #include <stdlib.h> #include <string.h>int main(void) {char source[] = "hello, world! welcome to china!"; //內容在棧區,可修改內容。char *input1 = source; //input1指向source,內容在棧區,所以input1可修改內容char *input2 = "hello, world! welcome to china!"; //input2所指內容在常量區,而input2本身在棧區//input2 = input1;(input2)[1] = 'a'; //嘗試修改常量區的內容,程序出錯printf("%c\n", (input1)[1]); printf("%c\n", (input2)[0]); //若只是讀相關內容,程序能正常運行return 0; }?
?
malloc/free與new/delete的區別
相同點:都可用于申請動態內存和釋放內存。
不同點:
(1) 操作對象有所不同。
malloc 與 free 是 C/C++ 的標準庫函數,new/delete 是 C++ 的運算符。對于非內部數據類的對象而言,光用 malloc/free 無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象消亡之前要自動執行析構函數。由于 malloc/free 是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加 malloc/free 。
(2) 用法上也有所不同。
函數 malloc 的原型如下:
用 malloc 申請一塊長度為 length 的整數類型的內存,程序如下:
int *p = (int*)malloc(sizeof(int) * length);注意:
① malloc 返回值的類型是 void *,所以在調用 malloc 時要顯式地進行類型轉換,將 void * 轉換成所需要的指針類型。
② malloc 函數本身并不識別要申請的內存是什么類型,它只關心內存的總字節數。
?
函數 free 的原型如下:
void free(void* memblock);為什么 free 函數不象 malloc 函數那樣復雜呢?原因是指針 p 的類型以及它所指的內存的容量事先都是知道的,語句 free(p) 能正確地釋放內存。如果 p 是 NULL 指針,那么 free 對 p 無論操作多少次都不會出問題。如果 p 不是 NULL 指針,那么 free 對 p 連續操作兩次就會導致程序運行錯誤。
?
?
new/delete 的使用要點
運算符 new 使用起來要比函數 malloc 簡單得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length); int *p2 = new int[length];這是因為 new 內置了 sizeof、類型轉換和類型安全檢查功能。對于非內部數據類型的對象而言,new 在創建動態對象的同時完成了初始化工作。如果對象有多個構造函數,那么 new 的語句也可以有多種形式。
如果用 new 創建對象數組,那么只能使用對象的無參數構造函數。例如:
在用 delete 釋放對象數組時,留意不要忘了符號'[ ]'。例如
delete []objects; // 正確的用法 delete objects; ??// 錯誤的用法后者相當于 delete objects[0],漏掉了另外 99 個對象。
(1) new 自動計算需要分配的空間,而 malloc 需要手工計算字節數。
(2) new 是類型安全的,而 malloc 不是,比如:
new operator 由兩步構成,分別是 operator new 和 construct 。
(3) operator new 對應于 malloc,但 operator new 可以重載,可以自定義內存分配策略,甚至不做內存分配,甚至分配到非內存設備上。而 malloc 無能為力。
(4) new 將調用 constructor,而 malloc 不能;delete 將調用 destructor,而 free 不能。
(5) malloc/free 要庫文件支持,new/delete 則不要。
?
?
本質區別
malloc/free 是 C/C++ 語言的標準庫函數,new/delete 是 C++ 的運算符。對于用戶自定義的對象而言,用 maloc/free 無法滿足動態管理對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由于 malloc/free 是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加于 malloc/free。因此C++需要一個能完成動態內存分配和初始化工作的運算符 new,以及一個能完成清理與釋放內存工作的運算符 delete。
#include <iostream> #include <malloc.h> using namespace std;class Obj { public:Obj( ){ cout << "Initialization" << endl; }~ Obj( ){ cout << "Destroy" << endl; }void Initialize( ){ cout << "Initialization" << endl; }void Destroy( ){ cout << "Destroy" << endl; } }obj;int main() {Obj *objects = new Obj[10];cout <<endl;//use malloc & freeObj*a = (Obj*)malloc(sizeof(obj));// allocate memorya->Initialize(); // initializationa->Destroy(); // deconstructionfree(a); // release memory//use new & deleteObj*b = new Obj;delete b;return 0; }?
問題:既然 new/delete 的功能完全覆蓋了 malloc/free,為什么 C++ 還保留 malloc/free 呢?
答:因為 C++ 程序經常要調用 C 函數,而 C 程序只能用 malloc/free 管理動態內存。如果用 free 釋放 "new創建的動態對象",那么該對象因無法執行析構函數而可能導致程序出錯。如果用 delete 釋放 "malloc申請的動態內存",理論上講程序不會出錯,但是該程序的可讀性很差。所以 new/delete、malloc/free 必須配對使用。
?
參考:https://blog.csdn.net/hackbuteer1/article/details/6789164
總結
以上是生活随笔為你收集整理的内存四区 malloc/free与new/delete的区别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++学习笔记:(十)异常
- 下一篇: C/C++线程基本函数