C++?內(nèi)存管理深入解析
C++ 內(nèi)存管理是程序設(shè)計(jì)的核心環(huán)節(jié),直接影響程序的性能、穩(wěn)定性和安全性。C++ 不像 Java、Python 等語(yǔ)言有自動(dòng)垃圾回收機(jī)制,而是需要開(kāi)發(fā)者手動(dòng)管理動(dòng)態(tài)內(nèi)存(或通過(guò)智能指針等機(jī)制自動(dòng)管理)。
1、C++ 內(nèi)存分區(qū)
| 內(nèi)存區(qū)域 | 存儲(chǔ)內(nèi)容 | 生命周期 | 管理方式 |
|---|---|---|---|
| 棧 (Stack) | 函數(shù)參數(shù)、局部變量、函數(shù)返回值等 | 自動(dòng)管理。在作用域開(kāi)始時(shí)分配,作用域結(jié)束時(shí)自動(dòng)釋放。 | 編譯器自動(dòng)生成代碼管理,效率極高。 |
| 堆/自由存儲(chǔ)區(qū) (Heap/Free Store) | 動(dòng)態(tài)分配的內(nèi)存 | 手動(dòng)管理。從 new 開(kāi)始到 delete 結(jié)束。 | 程序員顯式控制。分配和釋放速度較慢,容易出錯(cuò)。 |
| 全局/靜態(tài)存儲(chǔ)區(qū) (Global/Static) | 全局變量、靜態(tài)變量(static)、字面量 | 整個(gè)程序運(yùn)行時(shí)。在 main 開(kāi)始前初始化,main 結(jié)束后銷毀。 | 編譯器管理。 |
| 常量區(qū) (Constant) | 字符串字面量和其他常量 | 整個(gè)程序運(yùn)行時(shí)。 | 編譯器管理。通常不可修改。 |
| 代碼區(qū) (Code/Text) | 程序的二進(jìn)制代碼(函數(shù)體) | 整個(gè)程序運(yùn)行時(shí)。 | 編譯器管理。 |
圖示:
+-----------------------+ | 棧 (Stack) | <- 高地址,向下增長(zhǎng) +-----------------------+ | ↓ | | | | ↑ | +-----------------------+ | 堆 (Heap) | <- 低地址,向上增長(zhǎng) +-----------------------+ | 全局/靜態(tài)區(qū) (Global) | +-----------------------+ | 常量區(qū) (Constants) | +-----------------------+ | 代碼區(qū) (Code/Text) | +-----------------------+
2、棧
- 特點(diǎn):
- 空間較?。ㄍǔ?MB),由操作系統(tǒng)自動(dòng)分配和釋放,遵循“先進(jìn)后出”(FILO)原則。
- 分配速度極快(僅需移動(dòng)棧指針),適合存儲(chǔ)短期存在的變量(如函數(shù)內(nèi)的局部變量)。
void stackExample() {
int x = 10; // `x` 在棧上分配
std::string name = "Alice"; // `name` 對(duì)象本身在棧上,但其內(nèi)部的動(dòng)態(tài)數(shù)據(jù)可能在堆上
double data[100]; // 數(shù)組 `data` 在棧上分配(如果100很大,可能導(dǎo)致棧溢出)
} // 作用域結(jié)束,`x`, `name`, `data` 被自動(dòng)銷毀。
// `std::string` 的析構(gòu)函數(shù)會(huì)被調(diào)用,釋放它可能占用的堆內(nèi)存。注意:不要返回指向棧內(nèi)存的指針或引用!
int* dangerousFunction() {
int localVar = 42;
return &localVar; // 嚴(yán)重錯(cuò)誤!返回后 localVar 已被銷毀,指針懸空。
}3、堆
- 特點(diǎn):
- 空間較大(通常幾 GB),生命周期由開(kāi)發(fā)者控制(需手動(dòng)申請(qǐng)和釋放),分配/釋放速度較慢(涉及內(nèi)存塊查找、鏈表維護(hù)等)。
- 內(nèi)存地址不連續(xù),頻繁分配/釋放可能產(chǎn)生內(nèi)存碎片。
3.1 動(dòng)態(tài)分配與釋放:new / delete
new 運(yùn)算符完成兩件事:1) 在堆上分配足夠的內(nèi)存;2) 在該內(nèi)存上構(gòu)造對(duì)象(調(diào)用構(gòu)造函數(shù))。delete 運(yùn)算符也完成兩件事:1) 調(diào)用對(duì)象的析構(gòu)函數(shù);2) 釋放該對(duì)象占用的內(nèi)存。
// 動(dòng)態(tài)分配一個(gè) int,并初始化為 5
int* ptr = new int(5);
// 動(dòng)態(tài)分配一個(gè) MyClass 對(duì)象,調(diào)用其構(gòu)造函數(shù)
MyClass* objPtr = new MyClass("Name", 10);
// ... 使用 ptr 和 objPtr ...
// 釋放內(nèi)存
delete ptr; // 釋放 int
delete objPtr; // 調(diào)用 ~MyClass(),然后釋放內(nèi)存
ptr = nullptr; // 良好實(shí)踐:釋放后立即置空,防止懸空指針
objPtr = nullptr;3.2 分配/釋放對(duì)象數(shù)組
// 動(dòng)態(tài)分配一個(gè)包含10個(gè)int的數(shù)組 int* arrayPtr = new int[10]; // 動(dòng)態(tài)分配3個(gè)MyClass對(duì)象,調(diào)用它們的默認(rèn)構(gòu)造函數(shù) MyClass* objArrayPtr = new MyClass[3]; // ... 使用數(shù)組 ... // 釋放數(shù)組內(nèi)存。必須使用 delete[]! delete[] arrayPtr; // 正確:釋放數(shù)組 delete[] objArrayPtr; // 正確:調(diào)用每個(gè)元素的析構(gòu)函數(shù),然后釋放內(nèi)存 // delete objArrayPtr; // 災(zāi)難性錯(cuò)誤!行為未定義。只會(huì)調(diào)用第一個(gè)元素的析構(gòu)函數(shù),然后錯(cuò)誤地釋放內(nèi)存。
3.3 new/delete和malloc/free
C++ 提供兩種動(dòng)態(tài)內(nèi)存管理方式:C 語(yǔ)言兼容的 malloc/free,以及 C++ 特有的 new/delete。
重要規(guī)則: 絕對(duì)不要混用! 用 new 分配的內(nèi)存必須用 delete 釋放;用 malloc() 分配的內(nèi)存必須用 free() 釋放。
3.3.1 malloc/free(C 風(fēng)格)
函數(shù)原型:
void* malloc(size_t size); // 分配 size 字節(jié)的內(nèi)存,返回 void*(需強(qiáng)轉(zhuǎn)) void free(void* ptr); // 釋放 ptr 指向的內(nèi)存(ptr 必須是 malloc 分配的地址)
特點(diǎn):
- 僅分配內(nèi)存,不調(diào)用對(duì)象的構(gòu)造函數(shù);釋放內(nèi)存時(shí),不調(diào)用析構(gòu)函數(shù)(僅適用于基本類型,不適合類對(duì)象)。
- 需手動(dòng)計(jì)算內(nèi)存大?。ㄈ?nbsp;
malloc(sizeof(int) * 5))。
示例:
int* p = (int*)malloc(sizeof(int)); // 分配 int 大小的內(nèi)存(未初始化) *p = 10; // 手動(dòng)賦值 free(p); // 釋放內(nèi)存(p 變?yōu)橐爸羔槪ㄗh置空) p = nullptr;
3.3.2 new/delete(C++ 風(fēng)格)
new/delete 是 C++ 對(duì)動(dòng)態(tài)內(nèi)存管理的增強(qiáng),不僅分配/釋放內(nèi)存,還會(huì)自動(dòng)調(diào)用對(duì)象的構(gòu)造函數(shù)和析構(gòu)函數(shù),是管理類對(duì)象的首選方式。
基本用法:
// 1. 分配單個(gè)對(duì)象 MyClass* obj = new MyClass(10); // 調(diào)用 MyClass(int) 構(gòu)造函數(shù) delete obj; // 調(diào)用 MyClass 析構(gòu)函數(shù),釋放內(nèi)存 // 2. 分配數(shù)組(必須用 new[] 和 delete[] 匹配) MyClass* arr = new MyClass[5]; // 調(diào)用 5 次 MyClass 默認(rèn)構(gòu)造函數(shù) delete[] arr; // 調(diào)用 5 次 MyClass 析構(gòu)函數(shù),釋放數(shù)組
new 的底層原理:new 操作分兩步:
調(diào)用 operator new(size_t) 分配內(nèi)存(類似 malloc);
在分配的內(nèi)存上調(diào)用對(duì)象的構(gòu)造函數(shù)。
delete 的底層原理:delete 操作分兩步:
調(diào)用對(duì)象的析構(gòu)函數(shù);
調(diào)用 operator delete(void*) 釋放內(nèi)存(類似 free)。
3.4 常見(jiàn)動(dòng)態(tài)內(nèi)存錯(cuò)誤
3.4.1 內(nèi)存泄漏 (Memory Leak)
分配了內(nèi)存但忘記釋放。cpp void leak() { int* ptr = new int(100); // ... 使用了 ptr ... return; // 忘記 delete ptr; 內(nèi)存泄漏! }
3.4.2 懸空指針 (Dangling Pointer)
指針指向的內(nèi)存已被釋放。cpp int* ptr = new int(50); delete ptr; // 內(nèi)存被釋放 *ptr = 10; // 錯(cuò)誤!ptr 現(xiàn)在是懸空指針,解引用它是未定義行為。
3.4.3 雙重釋放 (Double Free)
對(duì)同一塊內(nèi)存釋放兩次。cpp int* ptr = new int(50); delete ptr; delete ptr; // 災(zāi)難!未定義行為,通常導(dǎo)致程序崩潰。
3.4.4 野指針 (Wild Pointer)
未初始化的指針。cpp int* ptr; // 野指針,指向隨機(jī)地址 *ptr = 10; // 極度危險(xiǎn)!未定義行為。
4、全局/靜態(tài)區(qū)
特點(diǎn):
- 全局變量和靜態(tài)變量(包括
static局部變量)存儲(chǔ)于此,程序啟動(dòng)時(shí)初始化,結(jié)束時(shí)銷毀。 static局部變量?jī)H在首次進(jìn)入函數(shù)時(shí)初始化,生命周期延續(xù)到程序結(jié)束。
示例:
int g_var = 10; // 全局變量,存儲(chǔ)在全局區(qū)
static int s_var = 20; // 靜態(tài)全局變量,存儲(chǔ)在全局區(qū)
void func() {
static int s_local = 30; // 靜態(tài)局部變量,存儲(chǔ)在全局區(qū)(僅初始化一次)
}5、常量區(qū)
- 特點(diǎn):
- 存儲(chǔ)字符串常量(如
"hello")和const修飾的常量,內(nèi)容只讀(修改會(huì)導(dǎo)致未定義行為)。
- 存儲(chǔ)字符串常量(如
- 示例:
const int c_var = 100; // const 常量,存儲(chǔ)在常量區(qū) char* str = "hello"; // "hello" 存儲(chǔ)在常量區(qū),str 是棧上的指針
6、常見(jiàn)問(wèn)題
- new/delete 和 malloc()/free() 有什么區(qū)別?
- new/delete 關(guān)心對(duì)象生命周期(構(gòu)造/析構(gòu)),而 malloc/free 只關(guān)心內(nèi)存塊。絕對(duì)不要混用。
- 什么是內(nèi)存泄漏?如何避免?
- 動(dòng)態(tài)分配的內(nèi)存不再被使用,但未被釋放,導(dǎo)致內(nèi)存浪費(fèi),長(zhǎng)期運(yùn)行可能耗盡內(nèi)存。
- 避免方法:
- 優(yōu)先使用棧對(duì)象:讓編譯器自動(dòng)管理生命周期。
- 使用智能指針:這是現(xiàn)代 C++ 最主要的手段。std::unique_ptr(獨(dú)占所有權(quán))和 std::shared_ptr(共享所有權(quán))會(huì)在析構(gòu)時(shí)自動(dòng)釋放內(nèi)存。
- 遵循 RAII 原則:將資源(內(nèi)存、文件句柄等)的獲取與對(duì)象的構(gòu)造函數(shù)綁定,釋放與析構(gòu)函數(shù)綁定。
- 成對(duì)使用 new/delete 和 new[]/delete[]:確保分配和釋放方式匹配。
- 使用工具檢測(cè):如 Valgrind、AddressSanitizer (ASan)、Visual Studio 診斷工具等。
- 為什么更推薦使用 std::make_shared 而不是直接 new?
- 異常安全:如果函數(shù)參數(shù)在表達(dá)式求值過(guò)程中拋出異常,make_shared 能保證已分配的內(nèi)存會(huì)被釋放,而直接 new 可能會(huì)泄漏。
- 性能:std::make_shared 通常只進(jìn)行一次內(nèi)存分配,同時(shí)容納對(duì)象本身和控制塊(引用計(jì)數(shù)等)。而 shared_ptr(new T(...)) 需要兩次分配(一次給對(duì)象,一次給控制塊)。
- 如何從 weak_ptr 安全地訪問(wèn)對(duì)象?
- 使用
lock()方法。它會(huì)返回一個(gè)std::shared_ptr。如果原始對(duì)象還存在,這個(gè) shared_ptr 是有效的;如果已被釋放,則返回一個(gè)空的 shared_ptr。必須檢查返回值。
- 使用
std::weak_ptr<MyClass> weak = ...;
if (auto shared = weak.lock()) { // 檢查返回的shared_ptr是否為空
// 對(duì)象還存在,可以安全使用 shared
shared->doSomething();
} else {
// 對(duì)象已被釋放
std::cout << "Object is gone.\n";
}- 什么是懸空指針 (Dangling Pointer) 和野指針 (Wild Pointer)?
懸空指針:指針指向的內(nèi)存已被釋放,但指針本身未被置空。解引用它是未定義行為
int* ptr = new int(10); delete ptr; // 內(nèi)存釋放 // ptr 現(xiàn)在是懸空指針 *ptr = 20; // 未定義行為! ptr = nullptr; // 良好實(shí)踐:釋放后立即置空。
野指針:未被初始化的指針,其值是隨機(jī)的垃圾地址。
int* ptr; // 野指針 *ptr = 10; // 極度危險(xiǎn)!未定義行為。 int* ptr2 = nullptr; // 正確:總是初始化指針。
- delete 和 delete[] 的區(qū)別是什么?混用會(huì)怎樣?
delete:用于釋放 new 分配的單個(gè)對(duì)象。它會(huì)調(diào)用該對(duì)象的析構(gòu)函數(shù)。
delete[]:用于釋放 new[] 分配的對(duì)象數(shù)組。它會(huì)調(diào)用數(shù)組中每個(gè)元素的析構(gòu)函數(shù),然后釋放整塊內(nèi)存。
混用的后果:未定義行為。最常見(jiàn)的后果是程序崩潰。
用 delete 釋放數(shù)組:只會(huì)調(diào)用第一個(gè)元素的析構(gòu)函數(shù),然后錯(cuò)誤地釋放內(nèi)存。
用 delete[] 釋放單個(gè)對(duì)象:會(huì)試圖析構(gòu)多個(gè)不存在的對(duì)象,導(dǎo)致內(nèi)存結(jié)構(gòu)被破壞。
- 什么是 RAII?它在 C++ 內(nèi)存管理中如何體現(xiàn)?
- RAII (Resource Acquisition Is Initialization):資源獲取即初始化。是 C++ 最重要的編程理念之一。
- 核心思想:將資源(內(nèi)存、文件句柄、鎖等)的生命周期與對(duì)象的生命周期綁定。
- 獲取資源:在對(duì)象的構(gòu)造函數(shù)中完成(例如,
std::ifstream打開(kāi)文件,std::unique_ptr分配內(nèi)存)。 - 釋放資源:在對(duì)象的析構(gòu)函數(shù)中完成(例如,
std::ifstream關(guān)閉文件,std::unique_ptr釋放內(nèi)存)。
- 獲取資源:在對(duì)象的構(gòu)造函數(shù)中完成(例如,
- 優(yōu)勢(shì):無(wú)論函數(shù)是正常返回還是因異常提前退出,局部對(duì)象都會(huì)在離開(kāi)作用域時(shí)被析構(gòu),從而保證資源一定能被釋放。智能指針是 RAII 用于內(nèi)存管理的完美體現(xiàn)。
- 設(shè)計(jì)一個(gè) unique_ptr,你會(huì)考慮哪些方面?
- 封裝一個(gè)原生指針作為成員。
- 刪除拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符(
= delete)以實(shí)現(xiàn)獨(dú)占語(yǔ)義。 - 實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符(
std::move)以支持所有權(quán)轉(zhuǎn)移。 - 在析構(gòu)函數(shù)中調(diào)用刪除器(默認(rèn)是
delete)釋放資源。 - 重載
operator*和operator->以提供指針式的訪問(wèn)。 - 提供
release(),reset(),get()等成員函數(shù)。 - (可選)支持自定義刪除器(作為模板參數(shù)的一部分)。
到此這篇關(guān)于C++ 內(nèi)存管理的文章就介紹到這了,更多相關(guān)C++ 內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
構(gòu)造函數(shù)不能聲明為虛函數(shù)的原因及分析
構(gòu)造函數(shù)不需要是虛函數(shù),也不允許是虛函數(shù),因?yàn)閯?chuàng)建一個(gè)對(duì)象時(shí)我們總是要明確指定對(duì)象的類型,盡管我們可能通過(guò)實(shí)驗(yàn)室的基類的指針或引用去訪問(wèn)它但析構(gòu)卻不一定,我們往往通過(guò)基類的指針來(lái)銷毀對(duì)象2013-10-10
C++11 學(xué)習(xí)筆記之std::function和bind綁定器
這篇文章主要介紹了C++11 學(xué)習(xí)筆記之std::function和bind綁定器,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
簡(jiǎn)單介紹C++編程中派生類的析構(gòu)函數(shù)
這篇文章主要介紹了C++編程中派生類的析構(gòu)函數(shù),析構(gòu)函數(shù)平時(shí)一般使用較少,需要的朋友可以參考下2015-09-09
Win10下最新版CLion(2020.1.3)安裝及環(huán)境配置教程詳解
這篇文章主要介紹了Win10下最新版CLion(2020.1.3)安裝及環(huán)境配置,CLion 是 JetBrains 推出的全新的 C/C++ 跨平臺(tái)集成開(kāi)發(fā)環(huán)境,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-08-08
C++利用靜態(tài)成員或類模板構(gòu)建鏈表的方法講解
這篇文章主要介紹了C++利用靜態(tài)成員或類模板構(gòu)建鏈表的方法講解,鏈表是基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),而在C++中構(gòu)件單鏈表還是稍顯復(fù)雜,需要的朋友可以參考下2016-04-04

