1. 程式人生 > >c++ new operator和operator new,delete operator和operator delete

c++ new operator和operator new,delete operator和operator delete

1 new operator 和 operator new,delete operator 和 operator delete

new operator: c++中的關鍵字new,如A *a = new A;

operator new:c++中的一個操作符,並且可以被過載(類似加減乘除操作符)

operator new can be called explicitly as a regular function, but in C++, new is an operator with a very specific behavior: An expression with the new operator, first calls function operator new (i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.

operator new和operator delete的宣告

void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object
 
void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array

例如:

A* a = new A;

分三步:

1 分配記憶體;2 呼叫A()構造物件;3 返回分配指標

事實上,分配記憶體這一操作就是由operator new(size_t)來完成的,如果類A過載了operator new,那麼將呼叫A::operator new(size_t ),否則呼叫全域性::operator new(size_t ),後者由C++預設提供。因此前面的步驟也就是:

1 呼叫operator new (sizeof(A)); 2 呼叫A::A(); 3 返回指標

下面的圖是一個具體的示例

簡單總結下:

  • 首先需要呼叫上面提到的 operator new 標準庫函式,傳入的引數為 class A 的大小,這裡為 8 個位元組,至於為什麼是 8 個位元組,你可以看看《深入 C++ 物件模型》一書,這裡不做多解釋。這樣函式返回的是分配記憶體的起始地址,這裡假設是 0x007da290。
  • 上面分配的記憶體是未初始化的,也是未型別化的,第二步就在這一塊原始的記憶體上對類物件進行初始化,呼叫的是相應的建構函式,這裡是呼叫 A:A(10); 這個函式,從圖中也可以看到對這塊申請的記憶體進行了初始化,var=10, file 指向開啟的檔案。
  • 最後一步就是返回新分配並構造好的物件的指標,這裡 pA 就指向 0x007da290 這塊記憶體,pA 的型別為類 A 物件的指標。

delete pA 的過程:

delete 就做了兩件事情:

  • 呼叫 pA 指向物件的解構函式,對開啟的檔案進行關閉。
  • 通過上面提到的標準庫函式 operator delete 來釋放該物件的記憶體,傳入函式的引數為 pA 的值,也就是 0x007d290。

2 operator new的三種形式

throwing (1)    
void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2) 
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3)   
void* operator new (std::size_t size, void* ptr) throw();

(1)(2)的區別僅是是否丟擲異常,當分配失敗時,前者會丟擲bad_alloc異常,後者返回null,不會丟擲異常。它們都分配一個固定大小的連續記憶體。

A* a = new A; //呼叫throwing(1)
A* a = new(std::nothrow) A; //呼叫nothrow(2)

3 new[] 和 delete[]

假設 class A *pAa = new A[3];

從這個圖中我們可以看到申請時在陣列物件的上面還多分配了 4 個位元組用來儲存陣列的大小,但是最終返回的是物件陣列的指標,而不是所有分配空間的起始地址。

這樣的話,釋放就很簡單了:

delete []pAa;

這裡要注意的兩點是:

  • 呼叫解構函式的次數是從陣列物件指標前面的 4 個位元組中取出;
  • 傳入 operator delete[] 函式的引數不是陣列物件的指標 pAa,而是 pAa 的值減 4。

4 為什麼 new/delete 、new []/delete[] 要配對使用?

4.1 使用new[]分配記憶體,delete釋放

內建型別:


int *pia = new int[10];
delete []pia;

內建型別因為不需要呼叫解構函式,所以使用new[]分配的記憶體的時候沒有多分配4個位元組儲存陣列大小;所以此時使用delete 釋放記憶體不會有問題

帶有自定義解構函式的類型別:

class A *pAa = new class A[3];
delete pAa;

問題有兩點,第一點是隻對第一個物件呼叫了解構函式,剩下兩個物件沒有呼叫解構函式,如果類物件中申請了大量的記憶體需要在解構函式中釋放,而你卻在銷燬陣列物件時少呼叫了解構函式,這會造成記憶體洩漏;

第二點是致命的,直接釋放 pAa 指向的記憶體空間,這個總是會造成嚴重的段錯誤,程式必然會崩潰!因為分配的空間的起始地址是 pAa 指向的地方減去 4 個位元組的地方。你應該將傳入引數設為那個地址!

4.2 使用new分配記憶體,delete[]釋放

同4.1進行分析

總的來說,記住一點即可:new/delete、new[]/delete[] 要配套使用總是沒錯的!

5 動態建立物件時的初始化問題

int *a = new int(5)  //allocates an integer, set to 5. (same syntax as constructors)

int *a = new int     //allocates an integer,明確不初始化,內建型別的物件無初始化

int *a = new int()  //allocates an integer,採用值初始化,將*a初始化為0

 string *ps = new string(10,'9')    //使用給定的值初始化該記憶體空間(十個9的字串)

string *ps = new string                //明確不初始化,但是string會使用預設建構函式初始化(initialized to empty string)

string *ps = new string()             //對於提供了預設建構函式的類型別,無論程式是明確的不初始化還是要求進行值初始化,都會自動呼叫預設建構函式初始化該物件

int *a = new int[5];

int *a = new int[5]();

原理同上(沒有 int *a = new int[5](9) 這種寫法)

參考文章: