C++內存管理:new與delete的深層解析

1. 引言

在C++的世界里,動態內存管理是一個核心話題。對于從C語言過渡到C++的開發者來說,一個常見的困惑是:既然C語言的mallocfree依然可以在C++中使用,為什么C++還要引入newdelete這兩個操作符?

本文將深入探討這兩對內存管理機制的根本區別,幫助你理解為何在C++中更推薦使用newdelete

2、本質區別:運算符vs函數

這就最根本的區別,決定了他們的行為與能力。

new和delete:是c++內置的運算符(operator),編譯器直接理解它們的含義,并腐惡轉換為底層的內存分配和對象構造調用。

malloc和free:是c語言標準庫中定義的庫函數,位于<stdlib>,<stdlib.h>頭文件中,它們的功能由運行時庫提供。

特性new/deletemalloc/free
本質c++操作符c庫函數
內存分配在堆上分配指定類型的內存在堆上分配指定字節數的內存
析構函數/構造函數會調用不會調用
返回值返回明確類型的指針返回void*,需手動強制轉換
分配失敗拋異常返回NULL
重載可以進行類成員重載或全局重載不可以重載
計算大小編譯器自動計算所需內存大小需手動使用sizeof計算字節數
初始化支持顯式初始化只能分配未初始化的內存

3、類型安全與返回值:

malloc的返回值是void*,所以在使用的時候必須使用強制轉換,如果忘記進行強制類型轉換,那么就會發生錯誤,這也是malloc函數的弊端或者說是缺點之一。

// C style (類型不安全)
int *p = (int*)malloc(sizeof(int)); // 必須進行強制類型轉換
*p = 10;
free(p);

new?直接返回所需類型的指針,是類型安全的。

// C++ style (類型安全)
int *p = new int; // 直接返回 int* 類型
*p = 10;
delete p;

接著往下看,這也是malloc與free函數與new和delete運算符的本質區別:

#include<iostream>
#include<stdlib.h>
using namespace std;
class Myclass
{
public:Myclass(){cout << "成功調用構造函數:Myclass()函數" << endl;}~Myclass(){cout << "成功調用析構函數:~Myclass()函數" << endl;}
};
int main()
{cout << "using malloc/free:" << endl;Myclass* obj1 = (Myclass*)malloc(sizeof(Myclass));free(obj1);//析構函數并不會被調用cout << "using new/delete:" << endl;Myclass* obj2 = new Myclass;delete obj2;//1、調用Myclass的析構函數,對于自定義類型,會調用它的析構函數//2、釋放內存,將對象本身的內存還給操作系統。
}

從輸出結構可以清晰地看到,malloc僅僅分配了足夠大的內存空間,而new在分配內存后,還調用了類的構造函數來對實例化出的對象進行初始化操作,delete在釋放內存前也會調用析構函數來清理資源(如關閉文件、釋放內存),而free則直接釋放內存,有可能導致內存泄漏,所以在c++里,十分建議寫new和delete,不僅僅安全還很方便,malloc與free有的,new和delete都有,malloc與free沒有的,new和delete也有。

4 、對于數組的處理:

使用free不需要關心是否是數組,但是如果你使用new[ ]進行內存分配,那么必須使用delete[ ]來釋放對應的內存,去掉這個[ ]可能會報錯!

接著看下面的代碼:

#include<iostream>
#include<stdlib.h>
using namespace std;
class Myclass
{
public:Myclass(){cout << "成功調用構造函數:Myclass()函數" << endl;}~Myclass(){cout << "成功調用析構函數:~Myclass()函數" << endl;}
};
int main()
{Myclass* ptr = new Myclass[10];delete [] ptr;
}

在c++中,使用new[ ]動態分配數組時,對于內置類型,比如int,char,float,等等,new int[10]不會調用其構造函數,因為內置類型沒有用戶定義的構造函數,但是對于自定義類型而言,使用new[ ]來定義數組,必須我這里自定義的類型Myclass,這里的new Myclass[10]會調用10次Myclass類的構造函數和析構函數。

下面介紹的才是這篇博客的核心內容,也是本人從淺至深的一個過程。

5、operator? new和operator delete:

提到new和delete,那么就不得不談到operator new和operator delete,那么operator new和operator delete有什么聯系或者說是區別呢?

為了方便我把代碼直接截圖下來,當我們寫出一句 Myclass* ptr=new Myclass[10]的時候,編譯器會將其分解為三個步驟:

1、分配內存:調用operator new(sizeof(Myclass)函數(這一點我等下會通過觀察匯編來進行驗證),申請一塊足夠大的,并且未初始化的原始內存。

2、構造對象:

在上述內存地址上調用Myclass::myclass()構造函數,初始化這塊內存,使其成為一個真正的對象。

3、返回指針:返回構造好的對象的地址,所以用指向這個類的指針來接收也就是Myclass*。

那么同理,delete obj也做了兩件事情:

1、析構對象:調用obj->~Myclass()析構函數,清理對象占用的內存

2、釋放資源:調用operator delete(obj)函數,釋放對象所占用的原始內存塊。

不僅僅如此,operator new與operator delete與new和delete:

new和delete是操作符,而operator new和operator delete是函數,換句說,operator new和operator delete就是C語言中malloc函數與free函數的加強版,它們不僅僅會開辟空間,還會在開辟空間的基礎上進行拋異常。而opertaor new和operator delete底層上也是調用了malloc函數與free函數。

下面我將通過匯編來驗證,new會通過調用operator new來開辟空間。

operator new?和?operator delete?做了什么?(單一職責)

這兩個函數只負責第一步和最后一步,即原始內存的分配與釋放。它們和?malloc/free?是同一級別的概念,但屬于C++的體系。

void* operator new(size_t size)

它的唯一任務就是接受一個字節數?size,找到一塊足夠大的連續內存空間,并返回指向這塊內存的?void*?指針。如果失敗,它默認拋出?std::bad_alloc?異常。

void operator delete(void* ptr)

它的唯一任務是接受一個由?operator new?返回的?void*?指針,釋放這塊內存。

標準庫已經提供了默認的全局?operator new?和?operator delete,它們通常就是基于?malloc?和?free?實現的。

你可以把它們想象成是C++世界里“高級的”、“會拋異常的”?malloc?和?free

6、?重載 (Overloading)

這才是重載的意義所在。我們可以提供我們自己版本的?operator new?和?operator delete?函數,來接管內存分配和釋放的過程。

第一種方式:

全局重載:

這種方式非常不推薦,因為程序中所有的new和delete都會調用我們自己寫的版本,這么做可以說是不安全的一種行為。

#include <iostream>
#include <stdlib.h> // for malloc, free
using namespace std;// 全局重載 operator new
void* operator new(size_t size) {cout << "Global new called, size: " << size << endl;void* p = malloc(size);if (!p) throw bad_alloc(); // 遵循規范,分配失敗拋異常return p;
}// 全局重載 operator delete
void operator delete(void* p) noexcept {cout << "Global delete called" <<endl;free(p);
}class MyClass { int data;
};
int main() {int* p1 = new int(42); // 會調用我們重載的全局 operator newdelete p1;             // 會調用我們重載的全局 operator deleteMyClass* obj = new MyClass; // 同樣會調用我們的版本delete obj;return 0;
}

第二種方式:

類特定重載 (非常有用且推薦):

#include <iostream>
#include <stdlib.h>
using namespace std;class MyClass {
public:int _data;// 類特定的 operator newstatic void* operator new(size_t size) {cout << "MyClass::new called, size: " << size <<endl;void* p = malloc(size);if (!p) throw bad_alloc();return p;}// 類特定的 operator deletestatic void operator delete(void* p) noexcept {cout << "MyClass::delete called" <<endl;free(p);}
};int main() {MyClass* obj = new MyClass; // 調用 MyClass::operator newdelete obj;                 // 調用 MyClass::operator deleteint* p = new int; // 仍然使用全局的 ::operator new,不受影響delete p;return 0;
}

我們只為我們特定的類重載?operator new?和?operator delete。這樣,只有分配和釋放這個類的對象時,才會使用我們自定義的版本,不會影響程序的其他部分。

下面將使用一表來把operator new與operator delete和new和delete之間的區別做個總結:

總結如下:

特性new/deleteoperator new/operator delete
身份操作符函數
職責完成的對象生命周期管理(分配內存加上構造或者析構)僅負責原始內存的分配和釋放
可重載性不可重載可以重載
調用關系調用operator new和構造函數被new所調用(已通過觀察匯編進行了對應的證明)

7、new?和?delete?表達式的編譯期魔法

最后一個部分:對上面的內容做一個更深層次的理解:

當編譯器看到?MyClass *obj = new MyClass;?這行代碼時,它會進行一個固定的分解動作。這個過程是理解重載底層原理的關鍵。

// 我們自己寫的代碼:
MyClass *obj = new MyClass(arg1, arg2);// 編譯器在背后實際生成的代碼:
void* __memory = nullptr; // 1. 先申請原始內存
try {__memory = MyClass::operator new(sizeof(MyClass)); // 尋找分配函數obj = static_cast<MyClass*>(__memory);obj->MyClass::MyClass(arg1, arg2); // 2. 在內存上構造對象(調用構造函數)
} catch (...) {if (__memory)MyClass::operator delete(__memory); // 如果構造失敗,釋放申請的內存throw; // 重新拋出異常
}

同理,delete也是一樣。

// 我們自己寫的代碼:
delete obj;// 編譯器在背后實際生成的代碼:
obj->~MyClass(); // 1. 先調用析構函數
MyClass::operator delete(obj); // 2. 再釋放內存

重載?operator new?和?operator delete,本質上就是告訴編譯器,在執行上述流程的第一步和最后一步時,不要用標準庫提供的默認函數,而是用我自定義的函數。

底層做了什么?

1、編譯時編譯器看到?new MyClass,知道要去?MyClass?的作用域內尋找?operator new?函數。

2、運行時:

new:調用我們自己重寫的Myclass::operator new來獲取內存,然后調用構造函數完成初始化。

delete:調用析構函數,然后調用我們自己重寫的Myclass::operator delete來釋放內存。

3、內存布局:重載函數本質是類的靜態成員函數,他們不屬于任何一個對象實例,因此沒有this指針,通俗地講,他們只是為對象的誕生(創建)和消亡(銷毀)提供場地管理的后勤部門!

截止到這里,本文的所有內容就完成了,在寫的時候難免有所不足之處,可在評論區指出,本人會及時進程更新,如對您有所幫助可以點贊加收藏,本作者持續更新c/c++或數據結構或linux有關的內容!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/97015.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/97015.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/97015.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【AI開發】【前后端全棧】[特殊字符] AI 時代的快速開發思維

&#x1f680; AI 時代的快速開發思維 —— 以 Django Vue3 為例的前后端分離快捷開發流程 一、AI 時代的開發新思路 在 AI 的加持下&#xff0c;軟件開發不再是“純體力活”&#xff0c;而是 思維工具自動化 的協作。 過去&#xff1a;需求 → 設計 → 開發 → 測試 → 上…

Day24_【深度學習(3)—PyTorch使用—張量的創建和類型轉換】

一、創建張量1.張量基本創建方式torch.tensor 根據指定數據創建張量 &#xff08;最重要&#xff09;torch.Tensor 根據形狀創建張量, 其也可用來創建指定數據的張量torch.IntTensor、torch.FloatTensor、torch.DoubleTensor 創建指定類型的張量1.1 torch.tensor# 方式一&…

3-12〔OSCP ? 研記〕? WEB應用攻擊?利用XSS提權

鄭重聲明&#xff1a; 本文所有安全知識與技術&#xff0c;僅用于探討、研究及學習&#xff0c;嚴禁用于違反國家法律法規的非法活動。對于因不當使用相關內容造成的任何損失或法律責任&#xff0c;本人不承擔任何責任。 如需轉載&#xff0c;請注明出處且不得用于商業盈利。 …

AI 大模型賦能智慧礦山:從政策到落地的全棧解決方案

礦山行業作為能源與工業原料的核心供給端&#xff0c;長期面臨 “安全生產壓力大、人工效率低、技術落地難” 等痛點。隨著 AI 大模型與工業互聯網技術的深度融合&#xff0c;智慧礦山已從 “政策引導” 邁入 “規模化落地” 階段。本文基于 AI 大模型智慧礦山行業解決方案&…

Node.js 項目依賴包管理

h5打開以查看 一、核心理念&#xff1a;從“能用就行”到“精細化管理” 一個規范的依賴管理體系的目標是&#xff1a; 可復現&#xff1a;在任何機器、任何時間都能安裝完全一致的依賴&#xff0c;保證構建結果一致。 清晰可控&#xff1a;明確知道每個依賴為何存在&#x…

洛谷P1835素數密度 詳解

題目如下&#xff1a;這里面有部分代碼比較有意思&#xff1a;1&#xff0c;為何開始先遍歷&#xff0c;最終值小于50000&#xff1f;因為題目要求的右邊與左邊差小于 10^6 &#xff0c;所以最多有10^3個素數&#xff0c;所以保存里面的素數數量大于1000&#xff0c;而50000的化…

突破限制:FileCodeBox遠程文件分享新體驗

文章目錄【視頻教程】1.Docker部署2.簡單使用演示3. 安裝cpolar內網穿透4. 配置公網地址5. 配置固定公網地址在隱私日益重要的今天&#xff0c;FileCodeBox與cpolar的協同為文件傳輸提供了安全高效的解決方案。通過消除公網IP限制和隱私顧慮&#xff0c;讓每個人都能掌控自己的…

以太網鏈路聚合實驗

一、實驗目的掌握使用手動模式配置鏈路聚合的方法掌握使用靜態 LACP 模式配置鏈路聚合的方法掌握控制靜態 LACP 模式下活動鏈路的方法掌握靜態 LACP 的部分特性的配置二、實驗環境安裝有eNSP模擬器的PC一臺&#xff0c;要求PC能聯網。三、實驗拓撲LSW1與LSW2均為S3700交換機。L…

autMan安裝教程

一、安裝命令 如果你系統沒安裝docker&#xff0c;請看往期教程 以下為通用命令 docker run -d --name autman --restart always -p 8080:8080 -p 8081:8081 -v /root/autman:/autMan --log-opt max-size10m --log-opt max-file3 hdbjlizhe/autman:latest解釋一下以上命令&…

【無人機】自檢arming參數調整選項

檢查項目 (英文名)中文含義檢查內容四旋翼建議 (新手 → 老手)理由說明All所有檢查啟用下面所有的檢查項目。? 強烈建議勾選這是最安全的設置&#xff0c;確保所有關鍵系統正常。Barometer氣壓計檢查氣壓計是否健康、數據是否穩定。? 必須勾選用于定高模式&#xff0c;數據異…

數字圖像處理(1)OpenCV C++ Opencv Python顯示圖像和視頻

Open CV C顯示圖像#include <iostream> #include <opencv2/opencv.hpp> using namespace cv;//包含cv命名空間 int main() {//imread(path)&#xff1a;從給定路徑讀取一張圖片&#xff0c;儲存為Mat變量對象Mat img imread("images/love.jpg");//named…

【芯片設計-信號完整性 SI 學習 1.2.2 -- 時序裕量(Margin)】

文章目錄1. 什么是時序裕量&#xff08;Margin&#xff09;1. 背景&#xff1a;為什么需要數字接口時序分析2. 時鐘周期方程3. Setup 裕量 (tMARGIN_SETUP)4. Hold 裕量 (tMARGIN_HOLD)5. 設計注意事項6. 實際應用場景2. 時序裕量的來源3. 測試方法(1) 眼圖測試 (Eye Diagram)(…

AOP 切面日志詳細

在業務方法上打注解package com.lib.service;Service public class BookService {LogExecution(description "查詢圖書")public Book query(int id) {return repo.findById(id);}LogExecution(description "借閱圖書")public void borrow(int id) {// 模…

使用paddlepaddle-Gpu庫時的一個小bug!

起初安裝的是 paddlepaddle 2.6.1版本。 用的是Taskflow的快速分詞以及ner快速識別&#xff1a;???????seg_accurate Taskflow("word_segmentation", mode"fast") ner Taskflow("ner", mode"fast")但是使用不了Gpu。想使用Gp…

量子能量泵:一種基于并聯電池與電容陣的動態直接升壓架構

量子能量泵&#xff1a;一種基于并聯電池與電容陣的動態直接升壓架構 摘要 本文提出了一種革命性的高效電源解決方案&#xff0c;通過創新性地采用并聯電池組與串聯高壓電容陣相結合的架構&#xff0c;徹底解決了低電壓、大功率應用中的升壓效率瓶頸與電池一致性難題。該方案摒…

【Linux網絡】網絡基礎概念——帶你打開網絡的大門

1. 計算機網絡背景 文章目錄1. 計算機網絡背景網絡發展2. 初識協議2.1 協議分層軟件分層的好處2.2 OSI七層模型2.3 TCP/IP五層(或四層)模型網絡發展 獨立模式 獨立模式是計算機網絡發展的最初階段&#xff0c;主要特點如下&#xff1a; 單機工作環境&#xff1a; 每臺計算機完…

簡單介紹一下Clickhouse及其引擎

一、ClickHouse 的優缺點一、ClickHouse 的優點 ? 1. 極致的查詢性能 列式存儲&#xff1a;只讀取查詢涉及的列&#xff0c;大幅減少 IO。數據壓縮&#xff1a;常見壓縮率 5~10 倍&#xff0c;減少存儲和帶寬消耗。向量化執行&#xff1a;按批次&#xff08;block&#xff09;…

【卷積神經網絡詳解與實例】8——經典CNN之VGG

1 開發背景 VGGNet是牛津大學視覺幾何組(Visual Geometry Group)提出的模型&#xff0c;該模型在2014ImageNet圖像分類與定位挑戰賽 ILSVRC-2014中取得在分類任務第二&#xff0c;定位任務第一的優異成績。其核心貢獻在于系統性地探索了網絡深度對性能的影響&#xff0c;并證明…

【分享】中小學教材課本 PDF 資源獲取指南

很多人都不知道&#xff0c;其實官方提供的中小學教材課本 PDF 文檔是完全免費且正版的&#xff0c;無需使用掃描版&#xff0c;清晰度和質量都非常高。 這些資源就藏在國家中小學智慧教育平臺&#xff08;basic.smartedu.cn&#xff09;上。這個平臺涵蓋了從小學到高中的各個…

js趣味游戲 貪吃蛇

以下是關于JavaScript趣味游戲的系統性整理&#xff0c;涵蓋經典案例、開發工具、教程資源及創意方向&#xff0c;助您快速掌握JS游戲開發的核心邏輯&#xff1a;一、經典JS趣味游戲案例貪吃蛇&#xff08;Snake Game&#xff09;核心機制&#xff1a;鍵盤控制蛇的移動方向&…