天天看點

【C++初階】C++記憶體管理

文章目錄

  • ​​一.C/C++記憶體分布圖​​
  • ​​二.new和delete記憶體管理​​
  • ​​1.對于内置類型​​
  • ​​2.對于自定義類型(重點)​​
  • ​​3.new和delete不比對問題(了解)​​
  • ​​4.new的底層機制(了解)​​
  • ​​5.定位new表達式(了解)​​
  • ​​三.面試題​​
  • ​​1.new/delete和malloc/free的差別(了解)​​
  • ​​2.記憶體洩漏​​

一.C/C++記憶體分布圖

作為C/C++方向的從業者,必須關注的四塊空間:
  1. 棧(局部資料)
  2. 堆(動态申請資料)
  3. 資料段(全局資料和靜态資料)
  4. 代碼段(可執行代碼和可讀常量)
【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";
  const 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++初階】C++記憶體管理
解析:
  1. globalVar定義在所有的函數外,是以是全局變量,位于資料區
  2. staticGlobalVar定義在函數體外[全局],且被static修飾[靜态],是以是靜态(全局)變量,位于資料區
  3. staticVar定義在函數體外[局部],且被static修飾[靜态],是以是靜态(局部)變量,位于資料區
  4. localVar定義在函數體内[局部],是以是局部變量,位于棧區
  5. num1是整型數組名,定義在函數體内[局部],是以是局部變量,位于棧區
  6. char2是字元數組名,定義在函數體内[局部],是以是局部變量,位于棧區
  7. *char2是字元數組存放的内容,位于棧區
  8. pChar3是一個指針,指向代碼段中常量字元串“abcd”,定義在函數體内[局部],位于棧區
  9. *pChar3是常量字元串“abcd”,位于代碼段
  10. ptr1指向動态申請的空間,定義在函數體内[局部],位于棧區
  11. *ptr1是動态申請的空間裡的内容,位于堆區

關于第7題和第9題差別:

【C++初階】C++記憶體管理

二.new和delete記憶體管理

C 語言中的malloc是函數,C++中的new是關鍵字,操作符,都是在堆上動态申請的空間

下面我針對内置類型和自定義類型比較new,delete和malloc,free

1.對于内置類型

C 語言和C++預設都沒有對各自動态申請的記憶體進行初始化
【C++初階】C++記憶體管理
int main()
{
  //C語言
  int* p1 = (int*)malloc(40);
  free(p1);


  //C++,預設不初始化
  int* ptr1 = new int;
  delete ptr1;
  //指定初始化
  int* ptr2 = new int(100);
  //ptr2 = nullptr;如果後面不使用了,可以置空
  delete ptr2;

  //動态申請數組
  //不初始化
  int* ptr3 = new int[10];
  delete[] ptr3;
  //完全初始化
  int* ptr4 = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };
  delete[] ptr4;
  //不完全初始化
  int* ptr5 = new int[10]{ 1,2,3,4,5 };
  delete[] ptr5;

  return 0;

}      
【C++初階】C++記憶體管理

對于内置類型:

new/delete相比與malloc/free,隻是用法上的差別

2.對于自定義類型(重點)

new/delete主要是針對自定義類型設計的,對于自定義類型,

new除了在堆上開辟空間,還會自動調用構造函數,完成對象的初始化

delete除了在堆上釋放空間,還會自動調用析構函數,完成對象的資源清理

class A
{
public:
  A(int a = 10)
    :_a(a)
  {
    cout << "構造函數" << endl;
  }
  ~A()
  {
    cout << "析構函數" << endl;
  }
private:
  int _a;
};

int main()
{
  A* ptr1 = new A;
  delete ptr1;
  cout << "____________________________________" << endl << endl;
  A* ptr2 = new A[4];
  delete[] ptr2;
  return 0;
}      
【C++初階】C++記憶體管理
案例:對于我們之前學過的單連結清單那塊

C語言:

ListNode* BuyListNode(int val)
{
    ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    newnode->_val = val;
    newnode->_next = nullptr;
}
int main()
{
    ListNode* n1 = BuyListNode(1);
    ListNode* n2 = BuyListNode(2);
    ListNode* n3 = BuyListNode(3);
    n1->_next = n2;
    n2->_next = n3;
    return 0;
}      

C++:

struct ListNode
{
  int _val;
  ListNode* _next;
  ListNode(int val = 0)
    :_val(val)
    ,_next(nullptr)
  {}
};


int main()
{
  //建立連結清單
  ListNode* n1 = new ListNode(1);
  ListNode* n2 = new ListNode(2);
  ListNode* n3 = new ListNode(3);
  ListNode* n4 = new ListNode(4);
  ListNode* n5 = new ListNode(5);
  n1->_next = n2;
  n2->_next = n3;
  n3->_next = n4;
  n4->_next = n5;
  return 0;
}      
【C++初階】C++記憶體管理

3.new和delete不比對問題(了解)

案例:不比對現象
//1.
  int* ptr1 = new int;
  delete[] ptr1;

  //2.
  int* ptr2 = new int[10];
  delete ptr2;

  //3.
  int* ptr3 = new int;
  free(ptr3);      

不比對後果:未定義,由于環境(linux還是windows或者不同編譯器)不同,結果不同,不要嘗試不比對

ps:這個問題不是記憶體洩漏問題(記憶體洩漏是不會報錯的,類似一種慢性病,報錯類似一種急性病)

4.new的底層機制(了解)

new的底層機制其實是調用operator new函數申請空間 + 調用構造函數初始化

而operator new申請空間的底層實作也是調用malloc, 是以new的效率并沒有比malloc高

封裝malloc,申請記憶體失敗,抛異常

封裝malloc隻是為了符合面向對象處理出現錯誤的處理方式—抛異常

【C++初階】C++記憶體管理
  • 我們其實可以手動調用operator new函數
【C++初階】C++記憶體管理
ps:operator new函數的使用方式和malloc一樣,唯一不同的是operator new開空間失敗不會傳回nullptr,而是抛異常.

給大家看一下調用new的時候的反彙編:

  • 内置類型
int main()
{
  int* a = new int;

  return 0;
}      
【C++初階】C++記憶體管理
這個call調用的是operator new函數
  • 自定義類型
struct ListNode
{
  int _val;
  ListNode* _next;
  ListNode(int val = 0)
    :_val(val)
    ,_next(nullptr)
  {}
};


int main()
{
  //建立連結清單
  ListNode* n1 = new ListNode(1);

  return 0;
}      
【C++初階】C++記憶體管理

第一個call是調用operator new函數

第二個call是調用構造函數

同理就有operator new[]函數,調用多次operator new

還有operator delete和operator delete[]函數

ps:我們知道new的底層機制,但是我們沒有必要使用operator new去實際程式設計.

5.定位new表達式(了解)

定位new表達式是在已配置設定的原始記憶體空間中調用構造函數初始化一個對象。

構造函數有點不一樣,在我們之前學的都不能顯式調用,但是定位new表達式就可以完成顯式調用

ps:析構函數可以顯式調用(下圖證明)

【C++初階】C++記憶體管理
class A
{
public:
  A(int a = 10)
    :_a(a)
  {
    cout << "構造函數" << endl;
  }
  ~A()
  {
    cout << "析構函數" << endl;
  }
private:
  int _a;
};

int main()
{
  A* ptr1 = (A*)malloc(sizeof(A));
  if (ptr1 == nullptr)
  {
    perror("malloc fail");
    exit(-1);
  }
  //定位New--- 對ptr1指向的這塊空間,顯示調用構造函數初始化
  new(ptr1)A(1);

  //ps:析構函數可以顯式調用
  ptr1->~A();
  free(ptr1);
  //上面兩行相當于delete ptr1;
  //上節課講過delete等同于 調用析構函數+operator delete(失敗抛異常)
  return 0;
}      

定位new案例:

我們聽說過記憶體池還有池化技術,那我百度了一下,我就給大家講一下我的了解:

舉一個例子:

山上有好多和尚,他們每天需要來山腳下挑已經過濾好的自來水喝,

但每一次都要排老長老長的隊,于是各個和尚都在自己家裡建水池蓄水,可以避免每天排隊,提高效率

于此同時也産生一個問題:蓄水池的水需要一個過濾裝置定時過濾雜質後才能飲用

上述的山腳下的自來水就類似new/malloc,挑山腳下的别人已經過濾好的純淨水就是調用new/malloc開辟空間并且開好的空間是已經初始化好的,

于是和尚建蓄水池蓄水就是建記憶體池,提高效率

記憶體池的水需要定時過濾就類似定位new,對記憶體池的空間進行初始化

三.面試題

1.new/delete和malloc/free的差別(了解)

malloc/free new/delete
函數 操作符
對内置類型和自定義類型都不初始化 對内置類型不初始化,對自定義類型初始化
申請空間時有時類型的大小需要計算 直接跟類型和個數
傳回值為void*,使用前要強轉 new直接傳回對應的指針類型
開辟空間失敗傳回null 開辟空間失敗抛異常
最大的差別是new/delete對于自定義類型能夠自動調用構造函數和析構函數

2.記憶體洩漏

ps:記憶體洩漏是指針丢了,而不是記憶體丢了(記憶體一直都在)—–-指針丢了就是找不到這塊空間了

(想想永不關閉的程式,比如背景伺服器就知道危害了)

繼續閱讀