C和C++的動態記憶體管理的區別

時光如初發表於2020-11-04

在這裡插入圖片描述

前言

C++是一個極度追求效能的語言,因此在所有語言之中,對於記憶體過分執著“較真”,這也就產生了記憶體管理!

1.C/C++分佈方式

  • C++是一個極度追求效能的語言,因此在所有語言之中,對於記憶體過分執著“較真”,這也就產生了記憶體管理!
    首先我們來看一段程式碼
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = {1, 2, 3, 4};
	char char2[] = "abcd";
	char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof (int)*4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4);
	free (ptr1);
	free (ptr3);
}

通過這段程式碼,來考驗考驗我們的C語言記憶體管理問題:
選擇題:
選項: A.棧 B.堆 C.資料段 D.程式碼段
globalVar在哪裡?C staticGlobalVar在哪裡?_C
staticVar在哪裡?C localVar在哪裡?A
num1 在哪裡?A
char2在哪裡?A *char2在哪裡?A
pChar3在哪裡?A *pChar3在哪裡?D
ptr1在哪裡?A *ptr1在哪裡?B
在這裡插入圖片描述
1.棧又被稱之為堆疊,非靜態區域性變數/函式引數/返回值等等都存放在棧之中。
2.記憶體對映段是高效的I/O對映方式,用於裝載一個共享的動態記憶體庫,可使用系統介面建立共享記憶體,做程式間通訊。
3.堆用域程式執行時動態記憶體的分配。
4.資料段是儲存全域性資料和靜態資料的。
5.程式碼段是放置可執行的程式碼和只讀常量。

2. C/C++語言動態記憶體管理方式

2.1C語言動態記憶體管理方式

malloc/calloc/realloc/free
是我們C語言中經常使用到的動態記憶體管理,
1.普通的陣列空間申請

void test1()
{
	int array[10];  //都為隨機值
	int array2[10] = { 1, 2, 3 };  //除過前三個,其它都為0
	int array3[10] = { 0 };  //所有位置都為0
}

2. malloc和calloc以及realloc的空間管理
void test2()
{
	//malloc:只進行空間申請,不進行初始化
	int* ptr = (int*)malloc(sizeof(int));
	*ptr = 4;
	
	//calloc: 進行空間申請 + 零初始化
	int* ptr2 = (int*)calloc(1, sizeof(int));
	
	//realloc:  第一個引數為nullptr/NULL, 功能等價於malloc
	int* ptr3 = (int*)realloc(nullptr, sizeof(int));
	//調整空間大小:
	// 1. 直接原地調整大小
	// 2. 重新開空間: 重新申請空間,內容拷貝,釋放原有空間
	int* ptr4 = (int*)realloc(ptr, sizeof(int) * 4);
	char* ptr5 = (char*)realloc(ptr2, sizeof(char));

	free(ptr3);
	free(ptr4);
	free(ptr5);
	//傳入realloc中的空間後續不需要顯式釋放,會導致二次釋放的問題
	/*free(ptr);
	free(ptr2);*/
}

2.或者realloc
在這裡插入圖片描述
在這裡插入圖片描述

2.2C++動態記憶體管理方式

雖然C語言的記憶體管理方式在C++中可以繼續使用,但是有些地方不僅無能為力而且使用起來還會有很多缺點,因此C++也是提出了適合自己的記憶體管理方式,那就是newdelete操作符來進行動態記憶體管理。

void test1()
{
	// 單個型別的空間:new + 型別
	// 連續空間:new + 型別[個數]
	// 單個型別空間申請 + 初始化: new + 型別(初始值)
	// 基本型別用new申請連續空間,不能初始化
	int* ptr3 = new int;
	int* ptr4 = new int[10];
	int* ptr5 = new int(5);  //初始化為5

	//釋放空間
	//單個空間: delete 指標
	//連續空間: delete[] 指標
	//申請和釋放的操作匹配使用: malloc free,  new delete,  new [] delete[]
	delete ptr3;
	delete ptr5;
	delete[] ptr4;
}
class Date{
public:
	Date(){
	
	}
}

void test2()
{
	//動態建立自定義型別的物件:
	//new:動態開空間 + 呼叫建構函式初始化
	//申請單個空間: new 自定義型別(引數列表)
	Date* pd = new Date(2020);
	Date* pd2 = new Date(2030);
	Date* pd4 = new Date;  //呼叫預設構造:無參,全預設

	//申請連續的空間:new 自定義型別[個數], 自動呼叫預設構造進行初始化,如果沒有預設構造,編譯器報錯
	Date* pd3 = new Date[10];

	//釋放自定義型別的空間
	//delete: 呼叫解構函式清理資源 + 釋放空間
	delete pd;
	delete pd2;
	delete pd4;
	//連續空間: 呼叫N次析構 +  釋放空間
	delete[] pd3;
}

在這裡插入圖片描述
區別:在申請自定義型別的空間時:new會呼叫建構函式,而delete會呼叫解構函式,而malloc和free不會。

3. operator new和operator delete

operator newoperator delete這兩者是系統所提供的全域性函式,new會在底層呼叫operator new來申請空間,delete會在底層呼叫operator delete來釋放空間,而在operator new和operator delete 實現中我們可以發現還是通過malloc來進行申請,通過free來進行釋放。

void test()
{
	//void* operator new(size_t n): 不是運算子過載函式,而是一個全域性函式
	//                            : 使用方式和malloc類似
	//                            : 封裝malloc + 異常
	//new 10;
	//new的執行過程(自定義型別):operator new --> malloc  --> 建構函式

	char* ptr = (char*) operator new(sizeof(char));
	char* ptr2 = (char*)malloc(sizeof(char));

	//void operator delete(void* ptr):不是運算子過載函式,而是一個全域性函式
	//                               :使用方式和free類似
	//                               :封裝free 
	// delete執行過程(自定義型別): 析構 --> operator delete --> free
	operator delete(ptr);
	free(ptr2);
	free(nullptr);
	operator delete(nullptr);
}

在這裡插入圖片描述
在這裡插入圖片描述

4.new和delete的實現原理

在這裡插入圖片描述
在這裡插入圖片描述

5new的表示式

void test()
{
	//new定位表示式,給予申請的空間進行初始化
	Date* pd = (Date*)malloc(sizeof(Date));
	//new定位表示式: new (地址) 型別(引數列表)
	//	:在已經開好的空間上顯式呼叫建構函式
	new (pd)Date(2030);
	Date* pd2 = (Date*)malloc(sizeof(Date));
	new (pd2)Date;
}

6面試題

1.malloc/free和new/delete的區別
malloc/free和new/delete的共同點是:都是從堆上申請空間,並且需要使用者手動釋放。不同的地方是:

malloc和free是函式,new和delete是操作符
malloc申請的空間不會初始化,new可以初始化
malloc申請空間時,需要手動計算空間大小並傳遞,new只需在其後跟上空間的型別即可
malloc的返回值為void*, 在使用時必須強轉,new不需要,因為new後跟的是空間的型別
malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常
申請自定義型別物件時,malloc/free只會開闢空間,不會呼叫建構函式與解構函式,而new在申請空間後會呼叫建構函式完成物件的初始化,delete在釋放空間前會呼叫解構函式完成空間中資源的清理

2,什麼是記憶體洩漏,記憶體洩漏的危害,如何避免記憶體洩漏
什麼是記憶體洩漏:記憶體洩漏指因為疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況。記憶體洩漏並不是指記憶體在物理上的消失,而是應用程式分配某段記憶體後,因為設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費。
記憶體洩漏的危害:長期執行的程式出現記憶體洩漏,影響很大,如作業系統、後臺服務等等,出現記憶體洩漏會導致響應越來越慢,最終卡死。
避免記憶體洩漏:實現防禦如智慧指標;時候差錯如洩漏檢測工具。

void MemoryLeaks()
{
	// 1.記憶體申請了忘記釋放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	
	// 2.異常安全問題
	int* p3 = new int[10];
	Func(); // 這裡Func函式拋異常導致 delete[] p3未執行,p3沒被釋放.
	delete[] p3;
}

相關文章