[C++] 深度剖析C_C++內存管理機制

Kevin的技術博客.png

文章目錄

  • 內存分布
    • 內存分布圖解
  • C語言中動態內存管理方式
    • malloc:
    • calloc
    • realloc
  • C++內存管理方式
    • 內置類型
    • **自定義類型**
  • operator new & operator delete
    • operator new & operator delete函數
      • operator new
      • operator delete
    • **new T[N]** 與**delete[]**
  • **定位new表達式(placement-new)**
    • 如何使用
    • 注意事項
  • malloc/free和new/delete的區別

類和對象三部曲:
[C++] 輕熟類和對象
[C++] 由淺入深理解面向對象思想的組成模塊
類和對象:C++11新特性與知識補充

內存分布

內存分布圖解

image.png

  1. 棧又叫堆棧–非靜態局部變量/函數參數/返回值等等,棧是向下增長的。
  2. 內存映射段是高效的I/O映射方式,用于裝載一個共享的動態內存庫。用戶可使用系統接口
    創建共享共享內存,做進程間通信。
  3. 堆用于程序運行時動態內存分配,堆是可以上增長的。
  4. 數據段–存儲全局數據和靜態數據。
  5. 代碼段–可執行的代碼/只讀常量

C語言中動態內存管理方式

malloc:

  • void* malloc(size_t size);
  • 功能:malloc函數用于在堆上分配一塊連續的內存空間。它接受一個參數,即所需內存的大小(以字節為單位),并返回指向這塊內存的指針。
  • 初始化:malloc不會對分配的內存進行初始化,內存中的內容是未定義的,可能是之前的值或者全零,具體取決于操作系統。
  • 使用場景:當不需要初始化內存或者特定初始化時使用。

calloc

  • void* calloc(size_t num, size_t size);
  • 功能:calloc也用于在堆上分配內存,但它接受兩個參數,分別是要分配的元素數量和每個元素的大小(以字節為單位)。calloc會確保分配的內存區域中的每個字節都被初始化為零。
  • 初始化:與malloc不同,calloc會將分配的內存全部初始化為零,這使得它適合用于數組或結構體等需要初始化為默認值的情況。
  • 使用場景:當需要一個清零的內存塊時使用,比如初始化數組。

realloc

  • void* realloc(void* ptr, size_t size);
  • 功能:realloc用于調整先前通過malloc、calloc或realloc分配的內存塊的大小。它接受兩個參數,第一個是之前分配的內存的指針,第二個是新的大小(可以比原來大也可以比原來小)。
  • 初始化:realloc不涉及初始化新分配的內存部分,如果擴大了內存塊,新增的部分通常也是未定義的值。
  • 使用場景:當原先分配的內存大小不再滿足需求,需要擴大或減小內存空間時使用。需要注意的是,如果減小內存空間,超出新大小的部分數據會被截斷。

C++內存管理方式

內置類型

// 動態申請一個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;// 其他方式
int* p3 = new int(0);
int* p4 = new int[10]{ 0 };
int* p5 = new int[10]{1,2,3,4,5}; // 未初始化的用0補齊

image.png

注意:申請和釋放單個元素的空間,使用new和delete操作符,申請和釋放連續的空間,使用new[]delete[]

自定義類型

A* p1 = (A*)malloc(sizeof(A)); // C
A* p2 = new A(1); // C++A* p1 = new A(1);
A* p2 = new A(2,2); // 隱式類型A aa1(1, 1);
A aa2(2, 2);
A aa3(3, 3);
A* p3 = new A[3]{aa1, aa2, aa3}; A* p4 = new A[3]{ A(1,1), A(2,2), A(3,3)}; // 匿名函數//A aa1 = { 1, 1 };
A* p5 = new A[3]{ {1,1}, {2,2}, {3,3} };

C++中推薦使用newdelete進行內存管理,使用這二者進行內存管理的特點為**“除了開空間還會調用構造函數和析構函數”(原理下章會提及)**

operator new & operator delete

operator new & operator delete函數

operator new

原理:

  • **內置類型:**與malloc相似
  • 自定義類型:
    1. 調用operator new函數申請空間
    2. 在申請的空間上執行構造函數,完成對象的構造

源碼如下

/*
operator new:該函數實際通過malloc來申請空間,當malloc申請空間成功時直接返回;申請空間
失敗,嘗試執行空 間不足應對措施,如果改應對措施用戶設置了,則繼續申請,否
則拋異常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;while ((p = malloc(size)) == 0)  // 通過malloc擴容if (_callnewh(size) == 0){// report no memory// 如果申請內存失敗了,這里會拋出bad_alloc 類型異常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);// 返回分配的內存指針
} 

通過分析源碼可得出:

  • 在底層會調用 **malloc** 分配內存:函數內部有一個 while 循環,通過 malloc 分配指定大小的內存。
  • **會自動拋異常:**當 malloc 返回 nullptr,則調用 _callnewh 嘗試處理內存不足的情況,若仍然無法分配內存,則拋出 std::bad_alloc 異常。
  • 在語法層面上會調用構造函數:new 操作符分配內存后,會在分配的內存上調用構造函數,完成對象的初始化。

operator delete

原理:

  • **內置類型:**與free基本類似
  • 自定義類型:
    1. 在空間上執行析構函數,完成對象中資源的清理工作
    2. 調用operator delete函數釋放對象的空間

源碼如下:

/*
operator delete: 該函數最終是通過free來釋放空間的
*/
void operator delete(void* pUserData)
{_CrtMemBlockHeader* pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData == NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* 獲取指針指向內存塊的頭信息 */pHead = pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg(pUserData, pHead->nBlockUse); // 使用_free_dbg進行內存的釋放__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
} 
/*
free的實現
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

源碼分析:

  • #define free(p) _free_dbg(p, _NORMAL_BLOCK)我們可以發現,free的底層其實是一個宏,最終還是使用 _free_dbg(p, _NORMAL_BLOCK)進行內存釋放。
  • 通過第一點分析可得,delete的底層也是通過free,或者說_free_dbg(p, _NORMAL_BLOCK)進行內存的釋放
  • 在語法層面上調用析構函數: 在釋放內存之前調用對象的析構函數,以確保對象持有的資源(如動態分配的內存、打開的文件等)得到正確釋放。

編譯器在處理 delete obj; 這行代碼時會生成以下等效的代碼:

if (obj != nullptr) {obj->~A();  // 顯式調用析構函數operator delete(obj);  // 調用 operator delete 釋放內存
}

new T[N]delete[]

new T[N]的原理

  1. 調用operator new[]函數,在operator new[]中實際調用operator new函數完成N個對
    象空間的申請
  2. 在申請的空間上執行N次構造函數

delete[]的原理

  1. 在釋放的對象空間上執行N次析構函數,完成N個對象中資源的清理
  2. 調用operator delete[]釋放空間,實際在operator delete[]中調用operator delete來釋
    放空間

定位new表達式(placement-new)

定位new表達式語法:void* operator new(size_t, void* place) noexcept { return place; }

  • 定位new表達式(Placement New Expression),或簡稱placement new,是C++中一種特殊的內存分配式,它允許你在已經分配好的內存區域內構造對象。與標準的new操作符不同,定位new不負責內存的分配,而是直接在你指定的內存地址上調用對象的構造函數。這對于實現內存池、重復利用已分配的內存塊、在特定內存位置(如共享內存)創建對象等場景非常有用。
  • 定位 new 表達式允許我們在預分配的內存上構造對象,并手動管理對象的生命周期,包括調用析構函數和釋放內存。這樣可以更好地控制內存分配和釋放過程,避免內存泄漏和資源未釋放的問題。

如何使用

舉例

#include <iostream>
#include <cstdlib> // for malloc and freeusing namespace std;class MyClass {
public:MyClass(int value) : value(value) {cout << "MyClass(int) constructor with value: " << value << endl;}~MyClass() {cout << "~MyClass() destructor with value: " << value << endl;}private:int value;
};int main() {// Step 1: Allocate raw memory using mallocsize_t numObjects = 3;void* rawMemory = malloc(numObjects * sizeof(MyClass));if (!rawMemory) {cerr << "Memory allocation failed!" << endl;return 1;}// Step 2: Use placement new to construct objects in the allocated memoryMyClass* objects = static_cast<MyClass*>(rawMemory);for (size_t i = 0; i < numObjects; ++i) {new (objects + i) MyClass(i * 10); // Construct MyClass objects with values 0, 10, 20}// Step 3: Use the objects (this step is trivial in this example)// Step 4: Manually call destructors for each objectfor (size_t i = 0; i < numObjects; ++i) {objects[i].~MyClass();}// Step 5: Free the allocated raw memoryfree(rawMemory);return 0;
}

步驟解析:

  1. 使用 malloc 分配原始內存:
size_t numObjects = 3;
void* rawMemory = malloc(numObjects * sizeof(MyClass));
if (!rawMemory) {cerr << "Memory allocation failed!" << endl;return 1;
}
  • 使用 malloc 函數分配一塊大小為 numObjects * sizeof(MyClass) 的連續內存,用來存放 3 個 MyClass 對象。
  • 如果內存分配失敗,程序會輸出錯誤信息并返回。
  1. 在分配的內存中,使用new構建對象:
MyClass* objects = static_cast<MyClass*>(rawMemory);
for (size_t i = 0; i < numObjects; ++i) {new (objects + i) MyClass(i * 10); // Construct MyClass objects with values 0, 10, 20
}
  • 使用 placement new 表達式在預分配的內存上構造 MyClass 對象。
  • 通過 static_castrawMemory 轉換為指向 MyClass 類型的指針。
  • for 循環中,調用定位 new 在內存地址 objects + i 上構造 MyClass 對象,分別傳入 0、10 和 20 作為構造函數參數。
  1. 對象的使用 (省略)

  2. 手動調用每個對象的析構函數進行析構

for (size_t i = 0; i < numObjects; ++i) {objects[i].~MyClass();
}
  • 在內存釋放之前,必須手動調用每個對象的析構函數,釋放對象的資源。
  • 使用 for 循環,調用每個對象的析構函數。
  1. 釋放掉原始分配的內存
free(rawMemory);

使用 free 函數釋放在步驟 1 中分配的原始內存。

注意事項

  1. 內存管理:使用定位new后,對象的生命周期管理完全由程序員負責。這意味著你不能使用普通delete來釋放這個對象,因為那會試圖釋放由malloc分配的內存,導致未定義行為。你應該直接調用對象的析構函數,并手動歸還內存:
A->~A(); // 手動調用析構函數
std::free(p1); // 釋放內存
  1. 內存對齊:確保提供的內存地址是正確對齊的,以便能夠容納特定類型的對象。如果不對齊,可能導致未定義行為。
  2. 安全性:使用定位new時,你需要確保所指定的內存區域足夠大,以容納完整的對象實例,包括可能的內部對齊填充。否則,可能會覆蓋周邊內存,引發嚴重錯誤。
  3. 標準庫支持:C++標準庫提供了一個全局的operator new(void*, std::size_t)重載,它不執行任何實際的內存分配,專門用于定位new表達式。這個重載是固定的,不能被用戶自定義版本替代。

malloc/free和new/delete的區別

malloc/free和new/delete的共同點是:

  • 都是從堆上申請空間,并且需要用戶手動釋放。

不同的地方是:

  1. mallocfree是函數,newdelete是操作符
  2. malloc申請的空間不會初始化,new可以初始化
  3. malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可,
    如果是多個對象,[]中指定對象個數即可
  4. malloc的返回值為void*, 在使用時必須強轉,new不需要,因為new后跟的是空間的類型
  5. malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new
    要捕獲異常
  6. 申請自定義類型對象時,malloc/free只會開辟空間,不會調用構造函數與析構函數,而new
    在申請空間后會調用構造函數完成對象的初始化,delete在釋放空間前會調用析構函數完成
    空間中資源的清理釋放


image.png

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

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

相關文章

vue 實現下拉框的數據是樹狀結構

頁面顯示效果 vue實現代碼 <el-form-item label"公司名稱" prop"comName"><el-select ref"select" v-model"queryParams.comName" placeholder"請選擇公司名稱" clearable size"small"change"handl…

可學習激活函數 Maxout

可學習激活函數 Maxout 是一種神經網絡中的激活函數&#xff0c;它在特征提取的過程中能夠學習到最優的激活方式&#xff0c;從而提高模型的表達能力和性能。Maxout 由 Ian Goodfellow 等人在2013年提出&#xff0c;是一種能夠在訓練過程中自適應地選擇激活函數的模型。 Maxou…

在 Windows 上開發.NET MAUI 應用_1.安裝開發環境

開發跨平臺的本機 .NET Multi-platform App UI (.NET MAUI) 應用需要 Visual Studio 2022 17.8 或更高版本&#xff0c;或者具有 .NET MAUI 擴展的最新 Visual Studio Code。要開始在 Windows 上開發本機跨平臺 .NET MAUI 應用&#xff0c;請按照安裝步驟安裝 Visual Studio 20…

分布式 I/O 系統Modbus TCP 耦合器BL200

BL200 耦合器是一個數據采集和控制系統&#xff0c;基于強大的 32 位微處理器設計&#xff0c;采用 Linux 操作系統&#xff0c;可以快速接入現場 PLC、SCADA 以及 ERP 系統&#xff0c; 內置邏輯控制、邊緣計算應用&#xff0c;支持標準 Modbus TCP 服務器通訊&#xff0c;以太…

SVN常用命令

VCS VCS&#xff08;Version Control System&#xff09;是版本控制系統的縮寫&#xff0c;它是一種用于管理和跟蹤軟件代碼變化的系統 SVN Subversion&#xff08;SVN&#xff09;是一個廣泛使用的版本控制系統&#xff0c;用于管理源代碼和文檔。在命令行中使用SVN涉及一系…

Blender使用(二)點線面基本操作

Blender使用之點線面 1.編輯模式 tab鍵進行切換&#xff0c;為了方便菜單調出&#xff0c;可以設置鍵位映射為拖動時的餅菜單。 設置好后&#xff0c;按住tab鍵移動鼠標(注意不要點擊鼠標)&#xff0c;即可彈出編輯菜單。 默認是點模式&#xff0c;在左上角可進行點線面的切換…

電腦型號數據源的性能提升:新一代技術的突破

隨著科技的不斷發展&#xff0c;電腦型號的數據源性能也得到了顯著的提升。新一代技術的突破使得電腦型號的數據源更加準確、全面且易于使用。本文將從代碼的角度解釋這一突破&#xff0c;并參考挖數據平臺的內容&#xff0c;向大家介紹電腦型號數據源的性能提升。 首先&#…

嘗試理解docker網絡通信邏輯

一、docker是什么 Docker本質是一個進程,宿主機通過namespace隔離機制提供進程需要運行基礎環境&#xff0c;并且通過Cgroup限制進程調用資源。Docker的隔離機制包括 network隔離&#xff0c;此次主要探討網絡隔離mount隔離hostname隔離user隔離pid隔離進程通信隔離 二、doc…

spring-boot2.x整合Kafka步驟

1.pom依賴添加 <properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</ma…

自學鴻蒙HarmonyOS的ArkTS語言<十二>wrapBuilder:組件工廠類封裝

// FactoryComponent.ets Builder function Radio1() {Column() {Text(單選組件&#xff1a;)Row() {Radio({ value: 1, group: radioGroup })Text(選項1)}Row() {Radio({ value: 2, group: radioGroup })Text(選項2)}}.margin(10) }Builder function Checkbox1() {Column() {T…

DP(5) | 完全背包 | Java | 卡碼52, LeetCode 518, 377, 70 做題總結

完全背包 感覺越寫越糊涂了&#xff0c;初始化怎么做的&#xff1f;遞推公式怎么來的&#xff1f; 卡碼52. 攜帶研究材料 https://kamacoder.com/problempage.php?pid1052 import java.util.*;public class Main {public static void main(String[] args) {Scanner sc new …

Java面試八股之Redis集群是怎么選擇數據庫的

在Redis集群中&#xff0c;數據被水平分割&#xff08;sharding&#xff09;到各個節點上&#xff0c;這意味著所有的鍵空間被分成16384個哈希槽&#xff08;hash slots&#xff09;&#xff0c;這些槽均勻地分布在集群中的各個節點上。Redis集群并不支持傳統的數據庫切換&…

xiuno兔兔超級SEO插件(精簡版)

xiuno論壇是一個一款輕論壇產品的論壇&#xff0c;但是對于這個論壇基本上都是用插件實現&#xff0c;一個論壇怎么能離開網站seo&#xff0c;本篇分享一個超級seo插件&#xff0c;自動sitemap、主動提交、自動Ping提交。 插件下載:tt_seo.zip

實驗11 數據庫日志及數據庫恢復

一、 實驗目的 了解Mysql數據庫系統中數據恢復機制和主要方法。 二、 實驗環境 操作系統&#xff1a;Microsoft Windows 7旗艦版&#xff08;32&64位&#xff09;/Linux。 硬件&#xff1a;容量足以滿足MySQL 5.7&#xff08;8.0&#xff09;安裝及后續實驗的使用。 軟件…

Python | Leetcode Python題解之第232題用棧實現隊列

題目&#xff1a; 題解&#xff1a; class MyQueue:def __init__(self):self.A, self.B [], []def push(self, x: int) -> None:self.A.append(x)def pop(self) -> int:peek self.peek()self.B.pop()return peekdef peek(self) -> int:if self.B: return self.B[-1…

什么叫圖像的中值濾波,并附利用OpenCV和MATLB實現均值濾波的代碼

圖像的中值濾波&#xff08;Median Filtering&#xff09;是一種非線性數字濾波技術&#xff0c;常用于圖像處理以減少噪聲&#xff0c;同時保留圖像邊緣細節。其基本思想是用圖像中某個窗口內像素的中值替代該窗口中心像素的值。具體步驟如下&#xff1a; 選擇窗口&#xff1a…

C++樹(二)【直徑,中心】

目錄&#xff1a; 樹的直徑&#xff1a; 樹的直徑的性質&#xff1a; 性質1&#xff1a;直徑的端點一定是葉子節點 性質2&#xff1a;任意點的最長鏈端點一定是直徑端點。 性質3&#xff1a;如果一棵樹有多條直徑,那么它們必然相交&#xff0c;且有極長連…

STM32中PC13引腳可以當做普通引腳使用嗎?如何配置STM32的TAMPER?

1.STM32中PC13引腳可以當做普通引腳使用嗎&#xff1f; 在STM32單片機中&#xff0c;PC13引腳可以作為普通IO使用&#xff0c;但需要進行一定的配置。PC13通常與RTC侵入檢測功能&#xff08;TAMPER&#xff09;復用&#xff0c;因此需要關閉TAMPER功能才能將其作為普通IO使用。…

服務端渲染框架:Nuxt.js 與 Next.js 的區別和對比

&#x1f49d;&#x1f49d;&#x1f49d;歡迎蒞臨我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:「stormsha的主頁」…

2024國家護網面試小結

24年國護馬上就要開始&#xff0c;基本上大部分藍隊紅隊都已經準備入場了 今年護網第一年變成常態化護網&#xff0c;由十五天突然變成了兩個月常態化&#xff0c;導致今年護網有很多項目整的七零八落 博主今年參加了三家廠商藍隊護網面試&#xff0c;在這邊分享一下護網面試…