文章目錄
前言
C++是一個極度追求性能的語言,因此在所有語言之中,對於內存過分執着“較真”,這也就產生了內存管理!
1. 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
- 棧又被稱之爲堆棧,非靜態局部變量/函數參數/返回值等等都存放在棧之中。
- 內存映射段是高效的I/O映射方式,用於裝載一個共享的動態內存庫,可使用系統接口創建共享內存,做進程間通信。
- 堆用域程序運行時動態內存的分配。
- 數據段是存儲全局數據和靜態數據的。
- 代碼段是放置可執行的代碼和只讀常量。
2. C/C++語言動態內存管理方式
2.1 C語言動態內存管理方式
malloc/calloc/realloc和free
是我們C語言中經常使用到的動態內存管理,
- 普通的數組空間申請
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.2 C++動態內存管理方式
雖然C語言得內存管理方式在C++中可以繼續使用,但是有些地方不僅無能爲力而且使用起來比較麻煩,因此C++也是提出了適合自己的內存管理方式,那就是new和delete
操作符來進行動態內存管理。
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 new和operator 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的實現原理
5. 定位new的表達式
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. 常見面試題
- 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在釋放空間前會調用析構函數完成空間中資源的清理
- 什麼是內存泄漏,內存泄漏的危害,如何避免內存泄漏
- 什麼是內存泄漏:內存泄漏指因爲疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內存泄漏並不是指內存在物理上的消失,而是應用程序分配某段內存後,因爲設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。
- 內存泄漏的危害:長期運行的程序出現內存泄漏,影響很大,如操作系統、後臺服務等等,出現內存泄漏會導致響應越來越慢,最終卡死。
- 避免內存泄漏:實現防禦如智能指針;時候差錯如泄漏檢測工具。
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;
}