C/C++之變量對(duì)象的創(chuàng)建棧與堆方式
在C/C++及基于其的框架中,變量/對(duì)象的創(chuàng)建方式分為棧上創(chuàng)建和堆上創(chuàng)建,二者的核心區(qū)別在于內(nèi)存的分配與管理方式,這直接影響了對(duì)象的生命周期、性能和使用場(chǎng)景。
一、基本概念:棧與堆的內(nèi)存區(qū)域本質(zhì)
在程序運(yùn)行時(shí),內(nèi)存主要分為棧(Stack)、堆(Heap)、全局/靜態(tài)存儲(chǔ)區(qū)、代碼區(qū)等。棧和堆是程序中最常用的兩種動(dòng)態(tài)內(nèi)存區(qū)域,但其管理邏輯完全不同:
- 棧(Stack):是一塊由編譯器自動(dòng)管理的內(nèi)存區(qū)域,遵循“后進(jìn)先出(LIFO)”原則。其大小在程序編譯時(shí)通常已確定(可通過(guò)編譯器設(shè)置調(diào)整,一般為幾MB)。
- 堆(Heap):是一塊由程序員手動(dòng)管理的內(nèi)存區(qū)域,大小不固定(理論上可達(dá)到系統(tǒng)可用內(nèi)存上限,如GB級(jí))。堆的分配與釋放需要顯式調(diào)用函數(shù)(如C++的
new/delete、C的malloc/free)。
二、棧上創(chuàng)建:自動(dòng)管理的“臨時(shí)內(nèi)存”
棧上創(chuàng)建的對(duì)象/變量,其內(nèi)存由編譯器自動(dòng)分配和釋放,無(wú)需程序員干預(yù)。
語(yǔ)法形式
直接通過(guò)變量定義創(chuàng)建,無(wú)需new關(guān)鍵字:
// 棧上創(chuàng)建基本類型 int a = 10; double b = 3.14; // 棧上創(chuàng)建對(duì)象(如Qt的QString) QString str = "棧上字符串"; // 棧上創(chuàng)建自定義類對(duì)象(如QDialog) QDialog dialog(this); // 父窗口為this,對(duì)象在棧上
核心特性
1.自動(dòng)分配與釋放
棧上對(duì)象的生命周期與“作用域”綁定:
- 進(jìn)入作用域(如函數(shù)調(diào)用、代碼塊
{})時(shí),編譯器自動(dòng)為其分配內(nèi)存(移動(dòng)棧指針); - 離開(kāi)作用域(如函數(shù)返回、代碼塊結(jié)束)時(shí),編譯器自動(dòng)釋放內(nèi)存(棧指針回退),無(wú)需手動(dòng)操作。
示例:
void func() {
QDialog dialog; // 進(jìn)入函數(shù),棧上創(chuàng)建dialog
dialog.exec(); // 使用對(duì)象
} // 離開(kāi)函數(shù),dialog自動(dòng)銷毀,內(nèi)存釋放
2.大小固定,分配速度極快
棧上的內(nèi)存大小在編譯時(shí)已確定(如局部變量的大小已知),分配時(shí)僅需移動(dòng)棧指針(一個(gè)CPU指令級(jí)操作),因此速度遠(yuǎn)快于堆。
3.生命周期嚴(yán)格受限
棧上對(duì)象無(wú)法在作用域之外訪問(wèn),一旦離開(kāi)作用域就會(huì)被銷毀。例如,不能返回棧上對(duì)象的指針(否則會(huì)成為“野指針”):
QDialog* bad_func() {
QDialog dialog; // 棧上創(chuàng)建
return &dialog; // 錯(cuò)誤!函數(shù)結(jié)束后dialog已銷毀,返回的指針指向無(wú)效內(nèi)存
}
4.內(nèi)存連續(xù),無(wú)碎片
棧上的內(nèi)存分配嚴(yán)格遵循“后進(jìn)先出”,內(nèi)存塊連續(xù),不會(huì)產(chǎn)生碎片(堆內(nèi)存可能因頻繁分配/釋放產(chǎn)生碎片)。
三、堆上創(chuàng)建:手動(dòng)管理的“動(dòng)態(tài)內(nèi)存”
堆上創(chuàng)建的對(duì)象/變量,其內(nèi)存需要程序員通過(guò)new(C++)或malloc(C)顯式分配,并通過(guò)delete或free手動(dòng)釋放。
語(yǔ)法形式
使用new關(guān)鍵字創(chuàng)建,返回指向?qū)ο蟮闹羔槪?/p>
// 堆上創(chuàng)建基本類型
int* a = new int(10);
// 堆上創(chuàng)建對(duì)象(如Qt的QString)
QString* str = new QString("堆上字符串");
// 堆上創(chuàng)建自定義類對(duì)象(如Qt的UI指針)
Ui::MyDialog* ui = new Ui::MyDialog(); // 常見(jiàn)于Qt界面類
核心特性
1.手動(dòng)分配與釋放
堆上對(duì)象的生命周期完全由程序員控制:
- 用
new分配內(nèi)存時(shí),編譯器會(huì)在堆上查找一塊足夠大的空閑內(nèi)存,返回其地址; - 必須用
delete手動(dòng)釋放(否則會(huì)導(dǎo)致內(nèi)存泄漏),釋放后指針應(yīng)置為nullptr(避免“野指針”)。
示例:
void func() {
QDialog* dialog = new QDialog(this); // 堆上創(chuàng)建
dialog->exec();
delete dialog; // 手動(dòng)釋放,否則內(nèi)存泄漏
dialog = nullptr; // 避免野指針
}
2.大小動(dòng)態(tài),生命周期靈活
堆上內(nèi)存的大小可在運(yùn)行時(shí)動(dòng)態(tài)確定(如根據(jù)用戶輸入分配數(shù)組),且對(duì)象的生命周期不受作用域限制:只要不調(diào)用delete,對(duì)象就一直存在,可跨函數(shù)、跨作用域訪問(wèn)。
示例:
QDialog* good_func() {
QDialog* dialog = new QDialog(); // 堆上創(chuàng)建
return dialog; // 正確:返回后仍可使用,需在外部釋放
}
// 調(diào)用者負(fù)責(zé)釋放
void caller() {
QDialog* d = good_func();
d->show();
delete d; // 手動(dòng)釋放
}
3.分配速度較慢,可能產(chǎn)生碎片
堆內(nèi)存分配時(shí),系統(tǒng)需要遍歷空閑內(nèi)存塊查找合適大小的區(qū)域(稱為“內(nèi)存分配算法”),速度遠(yuǎn)慢于棧;頻繁分配/釋放不同大小的堆內(nèi)存,會(huì)導(dǎo)致內(nèi)存碎片(空閑塊過(guò)小無(wú)法利用)。
4.通過(guò)指針間接訪問(wèn)
堆上對(duì)象的地址存儲(chǔ)在指針中,必須通過(guò)指針間接訪問(wèn)(如dialog->exec()),而棧上對(duì)象可直接通過(guò)變量名訪問(wèn)(如dialog.exec())。
四、棧上創(chuàng)建與堆上創(chuàng)建的核心區(qū)別對(duì)比
| 對(duì)比維度 | 棧上創(chuàng)建 | 堆上創(chuàng)建 |
|---|---|---|
| 內(nèi)存管理 | 編譯器自動(dòng)分配/釋放(無(wú)需手動(dòng)操作) | 程序員手動(dòng)分配(new)/釋放(delete) |
| 生命周期 | 與作用域綁定(離開(kāi)作用域自動(dòng)銷毀) | 與delete綁定(不釋放則一直存在) |
| 大小限制 | 受棧大小限制(通常幾MB,溢出會(huì)崩潰) | 受系統(tǒng)內(nèi)存上限限制(可至GB級(jí)) |
| 分配速度 | 極快(移動(dòng)棧指針,CPU指令級(jí)) | 較慢(需查找空閑內(nèi)存塊) |
| 內(nèi)存連續(xù)性 | 連續(xù)(無(wú)碎片) | 可能碎片化(頻繁分配/釋放后) |
| 訪問(wèn)方式 | 直接通過(guò)變量名訪問(wèn) | 通過(guò)指針間接訪問(wèn) |
| 安全性 | 無(wú)內(nèi)存泄漏風(fēng)險(xiǎn),但可能棧溢出 | 易內(nèi)存泄漏、double free(重復(fù)釋放)風(fēng)險(xiǎn) |
| 語(yǔ)法形式 | QDialog dialog;(直接定義) | QDialog* dialog = new QDialog();(指針) |
| 典型場(chǎng)景 | 局部變量、短期使用的小對(duì)象 | 大對(duì)象、跨作用域?qū)ο蟆?dòng)態(tài)大小對(duì)象 |
五、應(yīng)用場(chǎng)景:何時(shí)用棧,何時(shí)用堆?
選擇創(chuàng)建方式的核心依據(jù)是對(duì)象的生命周期和大小:
優(yōu)先用棧上創(chuàng)建的場(chǎng)景
對(duì)象生命周期與作用域一致:如函數(shù)內(nèi)的臨時(shí)變量、局部工具類(如循環(huán)計(jì)數(shù)器、臨時(shí)字符串)。
示例:Qt中模態(tài)對(duì)話框(exec()阻塞至關(guān)閉,生命周期與函數(shù)一致):
void showDialog() {
QMessageBox msg(this); // 棧上創(chuàng)建
msg.setText("提示");
msg.exec(); // 關(guān)閉后自動(dòng)銷毀,無(wú)需手動(dòng)釋放
}
對(duì)象較小:棧的分配速度優(yōu)勢(shì)明顯,適合int、double、小型結(jié)構(gòu)體等。
避免內(nèi)存管理負(fù)擔(dān):棧上對(duì)象無(wú)需擔(dān)心泄漏,適合簡(jiǎn)單邏輯。
優(yōu)先用堆上創(chuàng)建的場(chǎng)景
對(duì)象生命周期長(zhǎng)于作用域:如跨函數(shù)傳遞的對(duì)象(如返回給調(diào)用者的對(duì)象)、全局管理的資源(如Qt的UI對(duì)象ui)。
示例:Qt中通過(guò)new創(chuàng)建UI指針(生命周期與窗口一致):
class MyWindow : public QWidget {
private:
Ui::MyWindow* ui; // 堆上創(chuàng)建,隨窗口銷毀而釋放
public:
MyWindow() {
ui = new Ui::MyWindow(); // 堆上分配
ui->setupUi(this);
}
~MyWindow() { delete ui; } // 手動(dòng)釋放
};
- 對(duì)象較大:如大數(shù)組(
int arr[1000000]在棧上會(huì)溢出,需用堆int* arr = new int[1000000])。 - 動(dòng)態(tài)大小的對(duì)象:大小需在運(yùn)行時(shí)確定(如根據(jù)用戶輸入分配內(nèi)存)。
- 多態(tài)場(chǎng)景:堆上創(chuàng)建的對(duì)象支持多態(tài)(通過(guò)基類指針指向派生類對(duì)象),而棧上對(duì)象的類型在編譯時(shí)已確定。
六、堆內(nèi)存管理的現(xiàn)代方案:智能指針
堆內(nèi)存的手動(dòng)管理(new/delete)容易出錯(cuò)(如泄漏、double free),現(xiàn)代C++推薦使用智能指針(std::unique_ptr、std::shared_ptr)自動(dòng)管理堆內(nèi)存,結(jié)合了堆的靈活性和棧的安全性:
std::unique_ptr:獨(dú)占所有權(quán),對(duì)象銷毀時(shí)自動(dòng)釋放內(nèi)存。std::shared_ptr:共享所有權(quán),引用計(jì)數(shù)為0時(shí)自動(dòng)釋放。
示例:
#include <memory>
void func() {
// 堆上創(chuàng)建對(duì)象,由unique_ptr自動(dòng)管理
std::unique_ptr<QDialog> dialog(new QDialog());
dialog->exec();
// 無(wú)需手動(dòng)delete,離開(kāi)作用域時(shí)unique_ptr自動(dòng)釋放內(nèi)存
}
棧上創(chuàng)建和堆上創(chuàng)建的本質(zhì)區(qū)別是內(nèi)存管理責(zé)任:棧由編譯器“包辦”,適合短期、小型、生命周期明確的對(duì)象;堆由程序員“掌控”,適合長(zhǎng)期、大型、動(dòng)態(tài)需求的對(duì)象。
在實(shí)際開(kāi)發(fā)中(如Qt),需根據(jù)對(duì)象的生命周期和大小靈活選擇,同時(shí)盡量使用智能指針等現(xiàn)代工具減少堆內(nèi)存管理風(fēng)險(xiǎn)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++段錯(cuò)誤(Segmentation fault)快速定位的解決方法
寫(xiě)過(guò)C++的朋友都知道,有時(shí)候程序編譯通過(guò),并不能代表程序就是對(duì)的,在linux下做開(kāi)發(fā)時(shí),經(jīng)常會(huì)遇到跑崩潰的情況,但是在終端只會(huì)報(bào)Segmentation fault,如果工程代碼量少,你還能重新debug一下慢慢找,本文給大家介紹了C++段錯(cuò)誤的快速定位,需要的朋友可以參考下2024-07-07
C++類與對(duì)象深入之靜態(tài)成員與友元及內(nèi)部類詳解
朋友們好,這篇播客我們繼續(xù)C++的初階學(xué)習(xí),現(xiàn)在對(duì)我們對(duì)C++的靜態(tài)成員,友元,內(nèi)部類知識(shí)點(diǎn)做出總結(jié),整理出來(lái)一篇博客供我們一起復(fù)習(xí)和學(xué)習(xí),如果文章中有理解不當(dāng)?shù)牡胤?還希望朋友們?cè)谠u(píng)論區(qū)指出,我們相互學(xué)習(xí),共同進(jìn)步2022-06-06
C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹(shù))實(shí)例詳解
這篇文章主要介紹了C++數(shù)據(jù)結(jié)構(gòu)之文件壓縮(哈夫曼樹(shù))實(shí)例詳解的相關(guān)資料,利用哈夫曼編碼的方式對(duì)文件進(jìn)行壓縮,并且對(duì)壓縮文件可以解壓,需要的朋友可以參考下2017-07-07
c語(yǔ)言string.h頭文件中所有函數(shù)示例詳解
這篇文章詳細(xì)介紹了C語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的字符串和內(nèi)存操作函數(shù),以str開(kāi)頭的字符串處理函數(shù)和以mem開(kāi)頭的內(nèi)存處理函數(shù),每種函數(shù)都有詳細(xì)的原型、功能描述和示例代碼,需要的朋友可以參考下2024-11-11
C++ 隨機(jī)數(shù)字以及隨機(jī)數(shù)字加字母生成的案例
這篇文章主要介紹了C++ 隨機(jī)數(shù)字以及隨機(jī)數(shù)字加字母生成的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Eclipse中C++連接mysql數(shù)據(jù)庫(kù)
這篇文章主要為大家詳細(xì)介紹了Eclipse中C++連接mysql數(shù)據(jù)庫(kù) ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
C++/類與對(duì)象/默認(rèn)成員函數(shù)@構(gòu)造函數(shù)的用法
這篇文章主要介紹了C++/類與對(duì)象/默認(rèn)成員函數(shù)@構(gòu)造函數(shù)的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06

