C++ ---釋放內存(new和delete)


                                                                                        C++ ---釋放內存(new和delete)

C++動態分配和釋放內存 @c.biancheng.net/view/206.html

-------------------------------------------------------------------------------------------------

C語言中,動態分配內存用 malloc() 函數,釋放內存用 free() 函數。如下所示:

  1. int *p = (int*) malloc( sizeof(int) * 10 ); //分配10個int型的內存空間
  2. free(p); //釋放內存

C++中,這兩個函數仍然可以使用,但是C++又新增了兩個關鍵字,new 和 delete:new 用來動態分配內存,delete 用來釋放內存。

用 new 和 delete 分配內存更加簡單:

  1. int *p = new int; //分配1個int型的內存空間
  2. delete p; //釋放內存

new 操作符會根據后面的數據類型來推斷所需空間的大小。

如果希望分配一組連續的數據,可以使用 new[]:

  1. int *p = new int[10]; //分配10個int型的內存空間
  2. delete[] p;

用 new[] 分配的內存需要用 delete[] 釋放,它們是一一對應的。

和 malloc() 一樣,new 也是在堆區分配內存,必須手動釋放,否則只能等到程序運行結束由操作系統回收。為了避免內存泄露,通常 new 和 delete、new[] 和 delete[] 操作符應該成對出現,並且不要和C語言中 malloc()、free() 一起混用。

在C++中,建議使用 new 和 delete 來管理內存,它們可以使用C++的一些新特性,最明顯的是可以自動調用構造函數和析構函數,后續我們將會講解。

--------------------------------------------------------------------------------------------------------------

數組的長度是預先定義好的,在整個程序中固定不變。C++ 不允許定義元素個數不確定的數組。例如:

  1. int n;
  2. int a[n]; //這種定義是不允許的

但是在實際的編程中,往往會出現所需的內存空間大小取決於實際要處理的數據多少,而實際要處理的數據數量在編程時無法確定的情況。如果總是定義一個盡可能大的數組,又會造成空間浪費。何況,這個“盡可能大”到底應該多大才夠呢?

為了解決上述問題,C++ 提供了一種“動態內存分配”機制,使得程序可以在運行期間,根據實際需要,要求操作系統臨時分配一片內存空間用於存放數據。此種內存分配是在程序運行中進行的,而不是在編譯時就確定的,因此稱為“動態內存分配”。

在 C++ 中,通過 new 運算符來實現動態內存分配。new 運算符的第一種用法如下:

T *p = new T;

其中,T 是任意類型名,p 是類型為 T* 的指針

這樣的語句會動態分配出一片大小為 sizeof(T) 字節的內存空間,並且將該內存空間的起始地址賦值給 p。例如:

  1. int* p;
  2. p = new int;
  3. *p = 5;

第二行動態分配了一片 4 個字節大小的內存空間,而 p 指向這片空間。通過 p 可以讀寫該內存空間。

new 運算符還有第二種用法,用來動態分配一個任意大小的數組:

T *p =new T[N];

其中,T 是任意類型名,p 是類型為 T* 的指針,N 代表“元素個數”,可以是任何值為正整數的表達式,表達式中可以包含變量、函數調用等。這樣的語句動態分配出 N × sizeof(T) 個字節的內存空間,這片空間的起始地址被賦值給 p。例如:

  1. int* pn;
  2. int i = 5 ;
  3. pn = new int[i*20];
  4. pn[0] = 20 ;
  5. pn[100] = 30;

最后一行編譯時沒有問題,但運行時會導致數組越界。因為上面動態分配的數組只有 100 個元素,pn[100] 已經不在動態分配的這片內存區域之內了。

如果要求分配的空間太大,操作系統找不到足夠的內存來滿足,那么動態內存分配就會失敗,此時程序會拋出異常。關於這一點,將在后續章節中介紹。

程序從操作系統動態分配所得的內存空間在使用完后應該釋放,交還操作系統,以便操作系統將這片內存空間分配給其他程序使用。C++ 提供 delete 運算符,用以釋放動態分配的內存空間。delete 運算符的基本用法如下:

delete p;

p 是指向動態分配的內存的指針。p 必須指向動態分配的內存空間,否則運行時很可能會出錯。例如:

  1. int* p = new int;
  2. *p = 5;
  3. delete p;
  4. delete p; //本句會導致程序出錯

上面的第一條 delete 語句正確地釋放了動態分配的 4 個字節內存空間。第二條 delete 語句會導致程序出錯,因為 p 所指向的空間已經釋放,p 不再是指向動態分配的內存空間的指針了。

如果是用 new 的第二種用法分配的內存空間,即動態分配了一個數組,那么釋放該數組時,應以如下形式使用 delete 運算符:

delete[] p;

p 依然是指向動態分配的內存的指針。例如:

  1. int* p = new int[20];
  2. p[0] = 1;
  3. delete[] p;

同樣地,要求被釋放的指針 p 必須是指向動態分配的內存空間的指針,否則會出錯。

如果動態分配了一個數組,但是卻用delete p的方式釋放,沒有用[],則編譯時沒有問題,運行時也一般不會發生錯誤,但實際上會導致動態分配的數組沒有被完全釋放。

牢記,用 new 運算符動態分配的內存空間,一定要用 delete 運算符釋放。否則,即便程序運行結束,這部分內存空間仍然不會被操作系統收回,從而成為被白白浪費掉的內存垃圾。這種現象也稱為“內存泄露”。

如果一個程序不停地進行動態內存分配而總是沒有釋放,那么可用內存就會被該程序大量消耗,即便該程序結束也不能恢復。這就會導致操作系統運行速度變慢,甚至無法再啟動新的程序。但是,只要重新啟動計算機,這種情況就會消失。

編程時如果進行了動態內存分配,那么一定要確保其后的每一條執行路徑都能釋放它。

另外還要注意,釋放一個指針,並不會使該指針的值變為 NULL。

---------------------------------------------------------------------------------------

通常定義變量或者對象,編譯器在編譯時都可以根據該變量或對象的類型知道所需內存空間的大小,從而系統在適當的時候為他們分配確定的存儲空間,這種內存分配被稱為靜態存儲分配。

  有些操作對象只有在程序運行時才能確定,這樣編譯器在編譯時就無法為他們預定存儲空間,只能在程序運行時,系統根據運行時的要求進行內存分配,這種方法稱為動態內存分配。

  所有動態存儲分配都在堆區中進行。

內存的分配與釋放
  當程序運行到需要一個動態分配的變量或對象,必須向系統申請取得堆中的一塊所需大小的存儲空間,用於存儲該變量或對象。當不再使用該變量或對象時,也就是它生命結束之時,要顯式釋放它所占用的存儲空間,這樣系統就能對該堆空間進行再分配,做到重復使用有限資源。

  在C++中,申請和釋放堆中分配的存儲空間,分別使用new和delete的兩個運算符來完成,使用格式如下:
  指針變量名 = new 類型名(初始化式)
    delete指針名 
  new運算符返回的是一個指向所分配類型變量(對象)的指針。對所創建的變量或對象,都是通過該指針來間接操作的,而動態創建的對象本身沒有名字。

復制代碼
#include<iostream>
using namespace std; class ST { private: int a; public: ST(int _a=0):a(_a) { this->a = _a; cout<<"Object was built. "<<endl; } ~ST() { cout<<"Object was free. "<<endl; } }; void malloc_free() { ST *tmp = (ST*)malloc(sizeof(ST)); free(tmp); } void new_delete() { ST *tmp = new ST[1];//但是new為對象數組分配空間不可初始化 delete []tmp; } void main() { ST *pt = new ST(1);//new為對象分配空間就可初始化 delete [] pt;//delete p 是刪除pt所指空間,而並非刪除pt,為了預防野指針可將pt=NUll malloc_free(); new_delete(); }
復制代碼

 運行結果

malloc&free,new&delete都是申請釋放空間,但是,有如下幾點不同

1.new申請時不需要強制轉換類型,也不需要申請結束后判斷是否申請到(因為其內部含有未申請到異常退出)

2.new在為某個對象申請空間時,會調用構造函數,因此可在申請時初始化(對象的構造函數要支持),delete也會在釋放空間時會先調用析構函數

3.由堆區創建對象數組(例如ST*pt = new ST[10]),只能調用缺省的構造函數(缺省構造函數:不含參數的或每個參數都有默認值的構造函數),不能調用其他任何構     造函數。若沒有缺省的構造函數則不能創建對象數組。還有,創建對象數組時不可以初始化。

4.對數組進行動態分配格式如下:
 指針變量名 = new 類型名[下標表達式]

 (下標表達式不是常量表達式,可不必再編譯時確定)
 delete []指向該數組的指針變量名
 這兩者必須搭配使用,若delete未加[],編譯器會認為該指針是指向數組的第一個元素的指針,僅僅回收第一個元素所占空間。加上[]則會回收整個數組。

@https://www.cnblogs.com/area-h-p/p/10339599.html

-------------------------------------------------------

new 和 delete 是 C++ 用於管理堆內存的兩個運算符,對應於C語言中的 malloc 和 free,但是 malloc 和 free 是函數,而new 和 delete 是運算符。除此之外,new 在申請內存的同時,還會調用對象的構造函數,而 malloc 只會申請內存;同樣,delete 在釋放內存之前,會調用對象的析構函數,而 free 只會釋放內存。

C++
new運算符申請內存:
將調用相應的 operator new(size_t) 函數動態分配內存,在分配到的動態內存塊上 初始化 相應類型的對象(構造函數)並返回其首地址。如果調用構造函數初始化對象時拋出異常,則自動調用 operator delete(void*, void*) 函數釋放已經分配到的內存。

delete運算符釋放內存:
調用相應類型的析構函數,處理類內部可能涉及的資源釋放,調用相應的 operator delete(void *) 函數。

int main()
{
    T * t = new T(); // 先內存分配,再構造函數
    delete t; // 先析構函數,再內存釋放
    return 0;
}

new表達式

type  * p_var = new type;   
//分配內存,但未初始化
int * a = new int;

type  * p_var = new type(init);
//分配內存時,將 *a 初始化為 8
int * a = new int(8);

type *p_var = new type [size];
//分配了3個int大小的連續內存塊,但未初始化
int * a = new int[3] ;

delete表達式

刪除單變量地址空間
int *a = new int;
delete a;//釋放單個int的空間

刪除數組空間
int *a = new int[5];
delete []a;//釋放int數組空間


C
內存區域可以分為棧,堆,靜態存儲區和常量存儲區。局部變量,函數形參,臨時變量都是在棧上獲得內存的,它們獲取的方式都是由編譯器自動執行的。
而C標准函數庫提供了許多函數來實現對堆上內存管理,其中包括:malloc函數,free函數,calloc函數和realloc函數。使用這些函數需要包含頭文件stdlib.h。

(1)malloc函數
malloc函數可以從堆上獲得指定字節的內存空間,其函數聲明如下:
void * malloc(int n);


其中,形參n為要求分配的字節數。如果函數執行成功,malloc返回獲得內存空間的首地址;如果函數執行失敗,那么返回值為NULL。由於malloc函數值的類型為void型指針,因此,可以將其值類型轉換后賦給任意類型指針,這樣就可以通過操作該類型指針來操作從堆上獲得的內存空間。

需要注意的是,malloc函數分配得到的內存空間是未初始化的。因此,一般在使用該內存空間時,要調用另一個函數memset來將其初始化為全0。memset函數的聲明如下:

void * memset (void * p,int c,int n) ;


該函數可以將指定的內存空間按字節單位置為指定的字符c。其中,p為要清零的內存空間的首地址,c為要設定的值,n為被操作的內存空間的字節長度。

int * p=NULL;
p=(int *)malloc(sizeof(int));
if(p==NULL){
    printf(“Can’t get memory!\n”);
}
memset(p,0,siezeof(int));

(2)free函數
從堆上獲得的內存空間在程序結束以后,系統不會將其自動釋放,需要程序員來自己管理。一個程序結束時,必須保證所有從堆上獲得的內存空間已被安全釋放,否則,會導致內存泄露。
void free (void * p);


由於形參為void指針,free函數可以接受任意類型的指針實參。
但是,free函數只是釋放指針指向的內容,而該指針仍然指向原來指向的地方,此時,指針為野指針,如果此時操作該指針會導致不可預期的錯誤。安全做法是:在使用free函數釋放指針指向的空間之后,將指針的值置為NULL。

free(p);
p=NULL;
//注:使用malloc函數分配的堆空間在程序結束之前必須釋放

(3)calloc函數
calloc函數的功能與malloc函數的功能相似,都是從堆分配內存。其函數聲明如下:
void *calloc(int n,int size);

函數返回值為void型指針。如果執行成功,函數從堆上獲得size X n的字節空間,並返回該空間的首地址。如果執行失敗,函數返回NULL。該函數與malloc函數的一個顯著不同時是,**calloc函數得到的內存空間是經過初始化的,其內容全為0。**calloc函數適合為數組申請空間,可以將size設置為數組元素的空間長度,將n設置為數組的容量。

 int * p=NULL;
//為p從堆上分配SIZE個int型空間

p=(int *)calloc(SIZE,sizeof(int));

if(NULL==p){
    printf("Error in calloc.\n");
    return -1;
}

free(p);
p = NULL;

(4)realloc函數
realloc函數的功能比malloc函數和calloc函數的功能更為豐富,可以實現內存分配和內存釋放的功能,其函數聲明如下:
void * realloc(void * p,int n);

其中,指針p必須為指向堆內存空間的指針,即由malloc函數、calloc函數或realloc函數分配空間的指針。realloc函數將指針p指向的內存塊的大小改變為n字節。如果n小於或等於p之前指向的空間大小,那么。保持原有狀態不變。如果n大於原來p之前指向的空間大小,那么,系統將重新為p從堆上分配一塊大小為n的內存空間,同時,將原來指向空間的內容依次復制到新的內存空間上,p之前指向的空間被釋放。relloc函數分配的空間也是未初始化的。

 int * p=NULL;
p=(int *)malloc(sizeof(int));

p=(int *)realloc(p,3*sizeof(int));

//釋放p指向的空間
realloc(p,0);
p=NULL;

注:使用malloc函數,calloc函數和realloc函數分配的內存空間都要使用free函數或指針參數為NULL的realloc函數來釋放。
 
原文鏈接:https://blog.csdn.net/dongxianfei/article/details/79031943


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM