本章目錄
- 1.C/C++記憶體分布
- 2. C語言中動態記憶體管理方式
- 2.1malloc/calloc/realloc和free
- 3. C++記憶體管理方式
- 3.1new/delete操作內置型別
- 3.2 new和delete操作自定義型別
- 4. operator new與operator delete函式
- 4.1operator new與oparetor delete函式
- 4.1 operator new與operator delete的類專屬多載
- 5. new和delete的實作原理
- 5.1 內置型別
- 5.2自定義型別
- 6. 定位new運算式(placement-new)
- 7.常見面試題
- 7.1 malloc/free和new/delete的區別
- 7.2 記憶體泄漏
- 7.2.1什么是記憶體泄漏,記憶體泄漏危害
- 7.2.2 記憶體泄漏分類
- 7.2.3 如何檢測記憶體泄漏
- 7.2.4 如何避免記憶體泄漏
1.C/C++記憶體分布
我們先來看下面的一段代碼和相關問題:

- 選擇題:
選項: A.堆疊 B.堆 C.資料段 D.代碼段
globalVar在哪里?C______________staticGlobalVar在哪里?C
staticVar在哪里?C_______________localVar在哪里?A
num1 在哪里?A_________________char2在哪里?A
*char2在哪里?D_________________pChar3在哪里?A
*pChar3在哪里?D________________ptr1在哪里?A
*ptr1在哪里?B - 填空題:
sizeof(num1) = 40;
sizeof(char2) = 5;__________strlen(char2) = 4;
sizeof(pChar3) = 4;_________strlen(pChar3) = 4;
sizeof(ptr1) = 4;

【說明】
- 堆疊又叫堆疊,非靜態區域變數/函式引數/回傳值等等,堆疊是向下增長的,
- 記憶體映射段是高效的IO映射方式,用于裝載一個共享的動態記憶體庫,用戶可使用系統介面創建共享記憶體,做行程間通信
- 堆用于程式運行時動態記憶體分配,堆是向上增長的
- 資料段——存盤靜態資料和全域資料
- 代碼段——可執行的代碼/只讀常量
2. C語言中動態記憶體管理方式
2.1malloc/calloc/realloc和free
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的區別是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 這里需要free(p2)嗎?
free(p3 );
}
【面試題】malloc/calloc/realloc的區別?
答:
malloc
這個函式向記憶體申請一塊連續可用的空間,并回傳指向這塊空間的指標,
- 如果開辟成功,則回傳一個指向開辟好空間的指標,
- 如果開辟失敗,則回傳一個NULL指標,因此malloc的回傳值一定要做檢查,
- 回傳值的型別是 void* ,所以malloc函式并不知道開辟空間的型別,具體在使用的時候使用者自己來決定,
- 如果引數 size 為0,malloc的行為是標準是未定義的,取決于編譯器,
calloc
- 函式的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個位元組初始化為0,
- 與函式 malloc 的區別只在于 calloc 會在回傳地址之前把申請的空間的每個位元組初始化為全0,
realloc
- realloc函式的出現讓動態記憶體管理更加靈活,
- 有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時候記憶體,
- 我們一定會對記憶體的大小做靈活的調整,那 realloc 函式就可以做到對動態開辟記憶體大小的調整,
- 回傳值為調整之后的記憶體起始位置,
- 這個函式調整原記憶體空間大小的基礎上,還會將原來記憶體中的資料移動到 新 的空間,
- realloc在調整記憶體空間的是存在兩種情況:
情況1:原有空間之后有足夠大的空間,在原有位置上進行開辟空間,
情況2:原有空間之后沒有足夠大的空間,將現有的資料拷貝到另一塊足夠的空間后,在開辟新空間,然后回傳記憶體新的起始位置,
3. C++記憶體管理方式
C語言記憶體管理方式在C++中可以繼續使用,但有些地方就無能為力而且使用起來比較麻煩,因此C++又提出了自己的記憶體管理方式:通過new和delete運算子進行動態記憶體管理
3.1new/delete操作內置型別
void Test()
{
//動態申請一個int型別的空間
int* ptr4 = new int;
//動態申請一個int型別的空間并初始化為10
int* ptr5 = new int(10);
//動態申請10個int型別的空間
int* ptr6 = new int[10];
delete ptr4;
delete ptr5;
delete []ptr6;//陣列釋放
}
【注意】:申請和釋放單個元素的空間,使用new和delete運算子,申請和釋放連續的空間,使用new[]和delete[]
3.2 new和delete操作自定義型別
class Test
{
public:
Test()
:_data(0)
{
cout << "Test():" << this << endl;
}
~Test()
{
cout << "~Test():" << this << endl;
}
private:
int _data;
};
void Test2()
{
//申請單個Test型別的空間
Test* p1 = (Test*)malloc(sizeof(Test));
free(p1);
//申請10個Test型別的空間
Test* p2 = (Test*)malloc(sizeof(Test)* 10);
free(p2);
}
void Test2()
{
//申請單個Test型別的物件
Test* p1 = new Test;
delete p1;
//申請10個Test型別的物件
Test* p2 = new Test[10];
delete[]p2;
}
【注意】:在申請自定義型別的空間時,new會呼叫建構式,delete會呼叫解構式,而malloc與free不會,
4. operator new與operator delete函式
4.1operator new與oparetor delete函式
new和delete是用戶進行動態記憶體申請和釋放的運算子,operator new和operator delete是系統提供的全域函式,new在底層呼叫operator new全域函式來申請空間,delete在底層通過operator delete全域函式來釋放空間,
class Test
{
public:
Test(int d=0): _data(d)
{
cout << "Test():" << this << endl;
}
~Test()
{
cout << "~Test():" << this << endl;
}
public:
void* operator new(size_t sz)
{
void *ptr = malloc(sz);
return ptr;
}
void operator delete(void *ptr)
{
free(ptr);
}
void* operator new[](size_t sz)
{
void *ptr = malloc(sz);
return ptr;
}
void operator delete[](void *ptr)
{
free(ptr);
}
private:
int _data;
};
void main()
{
Test *pt = new Test[10];
delete []pt;
}
通過上述,我們可以知道operator new實際是通過malloc來申請空間,oparetor delete最終是通過free來釋放空間的,
4.1 operator new與operator delete的類專屬多載
下面代碼演示了,針對鏈表的節點ListNode通過多載類專屬 operator new/ operator delete,實作鏈表節點使用記憶體池申請和釋放記憶體,提高效率,
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
int main()
{
List l;
return 0;
}
5. new和delete的實作原理
5.1 內置型別
如果申請的是內置型別的空間,new和malloc,delete和free基本類似,不同的地方是:new/delete申請和釋放的單個元素的空間,new[]和delete[]申請的是連續空間,而且new在申請失敗時會拋出例外,malloc會回傳NULL,
5.2自定義型別
- new的原理
- 呼叫opreator new函式申請空間
- 在申請空間上執行建構式,完成物件的初始化構造
- delete的原理
- 在空間上執行解構式,完成物件中資源的清理作業
- 呼叫operator delete函式釋放物件的空間
- new T[N]的原理
- 呼叫operator new[]函式,在operator new[]中實際呼叫operator new函式完成N個物件空間的申請
- 在申請的空間上執行N次建構式
- delete []的原理
- 在釋放的空間上執行N次解構式,完成N個物件中的資源的清理
- 呼叫operator delete[]釋放空間,實際在opterator delete[]中呼叫operator delete來釋放空間
6. 定位new運算式(placement-new)
定位new運算式是在已分配的原始記憶體空間中呼叫建構式初始化一個物件,
使用格式:
new (place_address) type或者new (place_address) type (initializer-list)
place_address必須是一個指標,initializer-list是型別的初始化串列
使用場景:
定位new運算式在實際中一般是配合記憶體池使用,因為記憶體池分配出的記憶體沒有初始化,所以如果是自定義型別的物件,需要使用new的定義運算式進行顯式調建構式進行初始化,
class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test()
{
// pt現在指向的只不過是與Test物件相同大小的一段空間,還不能算是一個物件,因為建構式沒有執行
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test; // 注意:如果Test類的建構式有引數時,此處需要傳參
}
在進行舉例說明:
int arr[10]={0};
new(arr) int(10);這里表達的意思就是arr[[0]=10;
new(arr,3) int(10);//arr[3]=10
- 函式形式:
void* operator new (size_t sz,int *ar,int pos)
{
return &arr[pos];
}
7.常見面試題
7.1 malloc/free和new/delete的區別
malloc/free和new/delete的共同點是:都從堆上申請空間,并且需要用戶手動釋放,不同的地方是:
- malloc和free是函式,new和delete是運算子
- malloc申請空間不會初始化,new可以初始化
- malloc事情空間時,需要手動計算大小并傳遞,new只需要在其后跟上空間的型別即可
- malloc的回傳值void*,在使用時必須強轉,new不需要,因為類后面是空間的型別
- malloc申請空間失敗時,回傳的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲例外
- 申請自定義型別物件時,malloc/free只會開辟空間,不會呼叫建構式和解構式,而new在申請空間后會呼叫建構式完成物件的初始化,delete在釋放空間前會呼叫解構式完成空間中資源的清理
7.2 記憶體泄漏
7.2.1什么是記憶體泄漏,記憶體泄漏危害
什么是記憶體泄漏:記憶體泄漏指因為疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況,記憶體泄漏并不是指記憶體在物理上的消失,而是應用程式分配某段記憶體后,因為設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費,
記憶體泄漏的危害:長期運行的程式出現記憶體泄漏,影響很大,如作業系統、后臺服務等等,出現記憶體泄漏會導致回應越來越慢,最終卡死,
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;
}
7.2.2 記憶體泄漏分類
C/C++程式中一般我們關心兩種方面的記憶體泄漏:
- 堆記憶體泄漏(Heap leak)
堆記憶體指的是程式執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一塊記憶體,用完后必須通過呼叫相應的 free或者delete 刪掉,假設程式的設計錯誤導致這部分記憶體沒有被釋放,那么以后這部分空間將無法再被使用,就會產生Heap Leak, - 系統資源泄漏
指程式使用系統分配的資源,比方套接字、檔案描述符、管道等沒有使用對應的函式釋放掉,導致系統資源的浪費,嚴重可導致系統效能減少,系統執行不穩定,
7.2.3 如何檢測記憶體泄漏
- 在linux下記憶體泄漏檢測:linux下幾款記憶體泄漏檢測工具
- 在windows下使用第三方工具:VLD工具說明
- 其他工具:記憶體泄漏工具比較
7.2.4 如何避免記憶體泄漏
- 工程前期良好的設計規范,養成良好的編碼規范,申請的記憶體空間記著匹配的去釋放,ps:這個理想狀態,但是如果碰上例外時,就算注意釋放了,還是可能會出問題,需要下一條智能指標來管理才有保證,
- 采用RAII思想或者智能指標來管理資源,
- 有些公司內部規范使用內部實作的私有記憶體管理庫,這套庫自帶記憶體泄漏檢測的功能選項,
- 出問題了使用記憶體泄漏工具檢測,ps:不過很多工具都不夠靠譜,或者收費昂貴,
總結一下:
記憶體泄漏非常常見,解決方案分為兩種:1、事前預防型,如智能指標等,2、事后查錯型,如泄漏檢測工具,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/277378.html
標籤:其他
上一篇:網路基本功:OSI模型、TCP/IP協議及味訓手、網路模式、主機網路配置
下一篇:網路常識(上)
