【校招面試 之 C/C++】第16題 C++ new和delete的實現原理


  

1、new

new操作針對數據類型的處理,分為兩種情況:
(1)簡單數據類型(包括基本數據類型和不需要構造函數的類型)
代碼實例:
int* p = new int;

匯編碼如下:

int* p = new int;
00E54C44  push        4  
00E54C46  call        operator new (0E51384h)  
00E54C4B  add         esp,4  

分析:傳入4byte的參數后調用operator new。其源碼如下:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                        _THROW_NCEE(_XSTD bad_alloc, );
                }
 
        return (p);
        }
分析:調用malloc失敗后會調用_callnewh。如果_callnewh返回0則拋出bac_alloc異常,返回非零則繼續分配內存。
這個_callnewh是什么呢?它是一個new handler,通俗來講就是new失敗的時候調用的回調函數。可以通過_set_new_handler來設置。下面舉個實例:
#include <stdio.h>
#include <new.h>
int MyNewHandler(size_t size)
{
	printf("Allocation failed.Try again");
	return 1;		//continue to allocate
	//return 0;		//stop allocating,throw bad_alloc
}
void main()
{
	// Set the failure handler for new to be MyNewHandler.
	_set_new_handler(MyNewHandler);
 
	while (1)
	{
		int* p = new int[10000000];
	}
}

在new基本數據類型的時候還可以指定初始化值,比如:

int* p = new int(4);

總結:

  • 簡單類型直接調用operator new分配內存;
  • 可以通過new_handler來處理new失敗的情況;
  • new分配失敗的時候不像malloc那樣返回NULL,它直接拋出異常。要判斷是否分配成功應該用異常捕獲的機制;

(2)復雜數據類型(需要由構造函數初始化對象)

代碼實例:

class Object
{
public:
	Object()
	{
		_val = 1;
	}
 
	~Object()
	{
	}
private:
	int _val;
};
 
void main()
{
	Object* p = new Object();
}

匯編碼如下:

Object* p = new Object();
00AD7EDD  push        4  
00AD7EDF  call        operator new (0AD1384h)  
00AD7EE4  add         esp,4  
00AD7EE7  mov         dword ptr [ebp-0E0h],eax  
00AD7EED  mov         dword ptr [ebp-4],0  
00AD7EF4  cmp         dword ptr [ebp-0E0h],0  
00AD7EFB  je          main+70h (0AD7F10h)  
00AD7EFD  mov         ecx,dword ptr [ebp-0E0h]  
00AD7F03  call        Object::Object (0AD1433h)        //在new的地址上調用構造函數
00AD7F08  mov         dword ptr [ebp-0F4h],eax  
00AD7F0E  jmp         main+7Ah (0AD7F1Ah)  
00AD7F10  mov         dword ptr [ebp-0F4h],0  
00AD7F1A  mov         eax,dword ptr [ebp-0F4h]  
00AD7F20  mov         dword ptr [ebp-0ECh],eax  
00AD7F26  mov         dword ptr [ebp-4],0FFFFFFFFh  
00AD7F2D  mov         ecx,dword ptr [ebp-0ECh]  
00AD7F33  mov         dword ptr [p],ecx  
總結:
new 復雜數據類型的時候先調用operator new,然后在分配的內存上調用構造函數。

 

2、delete

delete也分為兩種情況:
(1)簡單數據類型(包括基本數據類型和不需要析構函數的類型)。
int *p = new int(1);
delete p;

delete的匯編碼如下:

delete p;
00275314  mov         eax,dword ptr [p]  
00275317  mov         dword ptr [ebp-0D4h],eax  
0027531D  mov         ecx,dword ptr [ebp-0D4h]  
00275323  push        ecx  
00275324  call        operator delete (0271127h) 

分析:傳入參數p之后調用operator delete,其源碼如下:

void operator delete( void * p )
{
    RTCCALLBACK(_RTC_Free_hook, (p, 0));
 
    free( p );
}
RTCCALLBACK默認是空的宏定義,所以這個函數默認情況下就是簡單的調用free函數。
總結:
delete簡單數據類型默認只是調用free函數。
 
(2)復雜數據類型(需要由析構函數銷毀對象)
代碼實例:
class Object
{
public:
	Object()
	{
		_val = 1;
	}
 
	~Object()
	{
		cout << "destroy object" << endl;
	}
private:
	int _val;
};
 
void main()
{
	Object* p = new Object;
	delete p;
}

部分匯編碼如下:

012241F0  mov         dword ptr [this],ecx  
012241F3  mov         ecx,dword ptr [this]  
012241F6  call        Object::~Object (0122111Dh)                          //先調用析構函數
012241FB  mov         eax,dword ptr [ebp+8]  
012241FE  and         eax,1  
01224201  je          Object::`scalar deleting destructor'+3Fh (0122420Fh)  
01224203  mov         eax,dword ptr [this]  
01224206  push        eax  
01224207  call        operator delete (01221145h)  
0122420C  add         esp,4 

總結:

delete復雜數據類型先調用析構函數再調用operator delete。
 
3、new 數組
new[]也分為兩種情況:
(1)簡單數據類型(包括基本數據類型和不需要析構函數的類型)。
new[] 調用的是operator new[],計算出數組總大小之后調用operator new。
值得一提的是,可以通過()初始化數組為零值,實例:
char* p = new char[32]();

等同於:

char *p = new char[32];
memset(p, 32, 0);
總結:
針對簡單類型,new[]計算好大小后調用operator new。
 
(2)復雜數據類型(需要由析構函數銷毀對象)
實例:
class Object
{
public:
	Object()
	{
		_val = 1;
	}
 
	~Object()
	{
		cout << "destroy object" << endl;
	}
private:
	int _val;
};
 
void main()
{
	Object* p = new Object[3];
}
new[]先調用operator new[]分配內存,然后在p的前四個字節寫入數組大小,最后調用三次構造函數。
實際分配的內存塊如下:
這里為什么要寫入數組大小呢?因為對象析構時不得不用這個值,舉個例子:
class Object
{
public:
	Object()
	{
		_val = 1;
	}
 
	virtual ~Object()
	{
		cout << "destroy Object" << endl;
	}
private:
	int _val;
};
 
class MyObject : public Object
{
public:
	~MyObject()
	{
		cout << "destroy MyObject" << endl;
	}
private:
	int _foo;
};
 
void main()
{
	Object* p = new MyObject[3];
	delete[] p;
}
釋放內存之前會調用每個對象的析構函數。但是編譯器並不知道p實際所指對象的大小。如果沒有儲存數組大小,編譯器如何知道該把p所指的內存分為幾次來調用析構函數呢?
總結:
針對復雜類型,new[]會額外存儲數組大小。
 
4、delete 數組
delete[]也分為兩種情況:
(1)簡單數據類型(包括基本數據類型和不需要析構函數的類型)。
delete和delete[]效果一樣
比如下面的代碼:
int* pint = new int[32];
delete pint;
 
char* pch = new char[32];
delete pch;
運行后不會有什么問題,內存也能完成的被釋放。看下匯編碼就知道operator delete[]就是簡單的調用operator delete。
總結:
針對簡單類型,delete和delete[]等同。
(2)復雜數據類型(需要由析構函數銷毀對象)
釋放內存之前會先調用每個對象的析構函數。
new[]分配的內存只能由delete[]釋放。如果由delete釋放會崩潰,為什么會崩潰呢?
假設指針p指向new[]分配的內存。因為要4字節存儲數組大小,實際分配的內存地址為[p-4],系統記錄的也是這個地址。delete[]實際釋放的就是p-4指向的內存。而delete會直接釋放p指向的內存,這個內存根本沒有被系統記錄,所以會崩潰(delete指針還是delete指針指向的內存空間)。
總結:
針對復雜類型,new[]出來的內存只能由delete[]釋放。
 
【總結】:
 1、new 
new操作針對數據類型的處理,分為兩種情況:
(1) 簡單數據類型(包括基本數據類型和不需要構造函數的類型)
  • 簡單類型直接調用operator new分配內存;
  • 可以通過new_handler來處理new失敗的情況;
  • new分配失敗的時候不像malloc那樣返回NULL,它直接拋出異常。要判斷是否分配成功應該用異常捕獲的機制;
(2)復雜數據類型(需要由構造函數初始化對象)
     new 復雜數據類型的時候先調用operator new,然后在分配的內存上調用構造函數。
 
2、delete 
delete也分為兩種情況:
(1) 簡單數據類型(包括基本數據類型和不需要析構函數的類型)
    delete簡單數據類型默認只是調用free函數。
(2)復雜數據類型(需要由析構函數銷毀對象)
     delete復雜數據類型先調用析構函數再調用operator delete。
 
3、new 數組
new[]也分為兩種情況:
(1) 簡單數據類型(包括基本數據類型和不需要析構函數的類型)
   針對簡單類型,new[]計算好大小后調用operator new。
(2)復雜數據類型(需要由析構函數銷毀對象)
   針對復雜類型,new[]會額外存儲數組大小。
 
4、delete 數組
delete[]也分為兩種情況:
(1) 簡單數據類型(包括基本數據類型和不需要析構函數的類型)
   針對簡單類型,delete和delete[]等同。
(2)復雜數據類型(需要由析構函數銷毀對象)
   針對復雜類型,new[]出來的內存只能由delete[]釋放。
 
 轉自:https://blog.csdn.net/passion_wu128/article/details/38966581  做略微修改


免責聲明!

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



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