c++中智能指針與裸指針的實(shí)現(xiàn)
前言
現(xiàn)在有一個(gè)shared共享指針,通過.get方法賦值給一個(gè)裸指針,如果共享指針本身聲明周期到了,裸指針還能繼續(xù)使用嗎?裸指針會(huì)影響智能指針生命周期嗎
一、說明
在這種情況下,當(dāng)shared_ptr的生命周期結(jié)束時(shí),它所管理的資源會(huì)根據(jù)引用計(jì)數(shù)來決定是否銷毀:
核心原理:
shared_ptr通過引用計(jì)數(shù)管理資源,當(dāng)最后一個(gè)shared_ptr被銷毀時(shí),引用計(jì)數(shù)變?yōu)?,資源會(huì)被自動(dòng)釋放。裸指針的影響:通過
get()方法獲取的裸指針只是資源的一個(gè)普通指針,它不會(huì)影響shared_ptr的引用計(jì)數(shù)。具體行為:
- 當(dāng)
shared_ptr生命周期結(jié)束時(shí),無論裸指針是否還在使用,shared_ptr都會(huì)正常遞減引用計(jì)數(shù) - 如果此時(shí)引用計(jì)數(shù)變?yōu)?,資源會(huì)被銷毀
- 這會(huì)導(dǎo)致裸指針變成懸空指針(dangling pointer),繼續(xù)使用會(huì)產(chǎn)生未定義行為
- 當(dāng)
示例說明:
#include <memory>
int main() {
int* raw_ptr = nullptr;
{
std::shared_ptr<int> shared_ptr = std::make_shared<int>(42);
raw_ptr = shared_ptr.get(); // 獲取裸指針
// 此時(shí)引用計(jì)數(shù)為1
} // shared_ptr生命周期結(jié)束,引用計(jì)數(shù)減為0,資源被銷毀
// 此時(shí)raw_ptr成為懸空指針,訪問它是危險(xiǎn)的
// *raw_ptr = 10; // 未定義行為!
return 0;
}
結(jié)論:shared_ptr會(huì)正常銷毀其管理的資源(當(dāng)引用計(jì)數(shù)為0時(shí)),與通過get()獲取的裸指針是否還在使用無關(guān)。這種用法存在風(fēng)險(xiǎn),可能導(dǎo)致懸空指針問題,應(yīng)盡量避免。
二、測(cè)試
執(zhí)行下面代碼
#include <memory>
#include <iostream>
int main() {
int* raw_ptr = nullptr;
{
std::shared_ptr<int> shared_ptr = std::make_shared<int>(42);
raw_ptr = shared_ptr.get();
}
std::cout << *raw_ptr << std::endl;
*raw_ptr = 10;
std::cout << *raw_ptr << std::endl;
return 0;
}
第一個(gè)輸出為垃圾值,第二次輸出是10,程序也沒有崩潰,這是為什么?
要理解這個(gè)現(xiàn)象,核心是抓住 未定義行為(Undefined Behavior, UB)的本質(zhì)——不是“必然崩潰”,而是“結(jié)果不可預(yù)測(cè)”。你的代碼能輸出10而非崩潰,只是一種“偶然的合法表象”,背后是內(nèi)存管理的底層邏輯在起作用:
第一步:先理清內(nèi)存的“銷毀”到底是什么
當(dāng) shared_ptr 生命周期結(jié)束時(shí),它做的事情是:
- 引用計(jì)數(shù)減為0,觸發(fā) 資源釋放(調(diào)用
delete銷毀堆上的int對(duì)象); - 釋放的本質(zhì)是:告訴操作系統(tǒng)“這塊堆內(nèi)存可以被重新分配給其他代碼使用了”,而不是“把這塊內(nèi)存的數(shù)據(jù)清零”或“標(biāo)記為不可訪問”。
簡(jiǎn)單說:堆內(nèi)存被 delete 后,數(shù)據(jù)可能還留在原地,只是它的“使用權(quán)”被回收了——你后續(xù)再訪問它,屬于“非法侵占”,但操作系統(tǒng)不會(huì)立刻阻止(除非觸發(fā)了內(nèi)存保護(hù)機(jī)制)。
第二步:為什么第一個(gè)輸出是“垃圾值”,第二個(gè)是10?
我們逐行拆解代碼執(zhí)行過程:
int* raw_ptr = nullptr;
{
std::shared_ptr<int> shared_ptr = std::make_shared<int>(42);
raw_ptr = shared_ptr.get(); // raw_ptr指向堆上的int(值為42)
} // shared_ptr銷毀:堆上的int被delete(使用權(quán)回收),但內(nèi)存數(shù)據(jù)可能還是42
// 第一次訪問*raw_ptr:非法訪問已釋放的堆內(nèi)存
std::cout << *raw_ptr << std::endl; // 輸出“垃圾值”或42,都是UB的表現(xiàn)
// 第二次訪問:給已釋放的內(nèi)存賦值10
*raw_ptr = 10; // 非法賦值,但內(nèi)存未被其他代碼復(fù)用,所以能“成功”寫入10
std::cout << *raw_ptr << std::endl; // 讀取剛寫入的10,輸出10
關(guān)鍵原因:
- 第一個(gè)輸出“垃圾值”:可能是堆內(nèi)存被
delete后,數(shù)據(jù)沒有被覆蓋(比如還是42),也可能是被 操作系統(tǒng)/其他代碼輕微修改(變成隨機(jī)值)——本質(zhì)是“讀取了不屬于你程序合法擁有的內(nèi)存”,結(jié)果隨機(jī),所以叫“垃圾值”。 - 第二個(gè)輸出10:因?yàn)樵谫x值
*raw_ptr = 10時(shí),這塊堆內(nèi)存還沒被其他代碼復(fù)用(操作系統(tǒng)沒把它分配給別的變量),所以你的程序“碰巧”還能操作這塊內(nèi)存(寫入10),后續(xù)讀取自然能拿到10。
這就像:你租了個(gè)房子(堆內(nèi)存),到期后退租(delete),鑰匙被房東收回(使用權(quán)回收),但你還能偷偷溜進(jìn)去(非法訪問)——房子里的家具(數(shù)據(jù))可能還在(垃圾值),你甚至能換套家具(賦值10),只要房東還沒把房子租給別人(內(nèi)存未復(fù)用),就不會(huì)被發(fā)現(xiàn)。
第三步:為什么程序沒崩潰?
崩潰的本質(zhì)是:程序觸發(fā)了操作系統(tǒng)的“內(nèi)存保護(hù)機(jī)制”(比如訪問了“完全不存在的內(nèi)存地址”“已被其他進(jìn)程占用的內(nèi)存”“無權(quán)限訪問的內(nèi)存”)。
而你的代碼中:
raw_ptr指向的是 曾經(jīng)合法的堆內(nèi)存地址(不是野指針指向的隨機(jī)無效地址);- 這塊內(nèi)存還沒被 操作系統(tǒng)分配給其他程序/線程,所以你的程序?qū)λ?ldquo;讀/寫”操作沒有觸發(fā)內(nèi)存保護(hù),自然不會(huì)崩潰。
但這是極端危險(xiǎn)的“運(yùn)氣”——如果在 shared_ptr 銷毀后、你訪問 raw_ptr 之前,有其他代碼(比如函數(shù)調(diào)用、動(dòng)態(tài)內(nèi)存分配)復(fù)用了這塊堆內(nèi)存,那么:
- 讀取
*raw_ptr會(huì)拿到其他代碼的數(shù)據(jù)(垃圾值); - 寫入
*raw_ptr = 10會(huì)覆蓋其他代碼的數(shù)據(jù),導(dǎo)致程序邏輯錯(cuò)亂(比如變量值莫名改變、函數(shù)返回錯(cuò)誤結(jié)果); - 更糟的是:如果這塊內(nèi)存被 操作系統(tǒng)標(biāo)記為“不可訪問”(比如后續(xù)分配給了更高權(quán)限的進(jìn)程),此時(shí)訪問會(huì)直接觸發(fā) 段錯(cuò)誤(SIGSEGV),程序崩潰。
核心結(jié)論:未定義行為的“不可預(yù)測(cè)性”
你的代碼能運(yùn)行且輸出10,不代表程序正確,只是UB的一種“溫和表現(xiàn)”。UB的可能結(jié)果包括:
- 程序正常運(yùn)行,輸出預(yù)期值(如你的情況);
- 輸出垃圾值;
- 程序崩潰(段錯(cuò)誤);
- 程序邏輯錯(cuò)亂(后續(xù)代碼運(yùn)行異常);
- 甚至在不同編譯器(GCC/Clang/MSVC)、不同優(yōu)化等級(jí)(O0/O2)下表現(xiàn)完全不同。
比如:如果開啟編譯器優(yōu)化(-O2),可能會(huì)直接優(yōu)化掉 *raw_ptr 的訪問,輸出完全無意義的值,甚至程序崩潰——因?yàn)榫幾g器會(huì)假設(shè)“程序不會(huì)執(zhí)行UB”,從而進(jìn)行激進(jìn)優(yōu)化。
如何避免這種問題?
永遠(yuǎn)不要用 shared_ptr.get() 獲取裸指針后,在 shared_ptr 銷毀后使用。正確做法:
- 讓
shared_ptr的生命周期覆蓋裸指針的使用(擴(kuò)大shared_ptr的作用域); - 若需長期持有“弱引用”,用
std::weak_ptr(不會(huì)增加引用計(jì)數(shù),可通過lock()安全判斷資源是否存在); - 直接傳遞
shared_ptr(而非裸指針),依賴其引用計(jì)數(shù)管理生命周期。
正確示例(擴(kuò)大 shared_ptr 作用域):
int main() {
std::shared_ptr<int> shared_ptr = std::make_shared<int>(42);
int* raw_ptr = shared_ptr.get(); // 裸指針的使用被shared_ptr生命周期覆蓋
std::cout << *raw_ptr << std::endl; // 合法,輸出42
*raw_ptr = 10; // 合法,修改的是shared_ptr管理的資源
std::cout << *raw_ptr << std::endl; // 合法,輸出10
return 0;
} // shared_ptr銷毀,資源正常釋放,無懸空指針
最終總結(jié)
你看到的“第一個(gè)輸出垃圾值、第二個(gè)輸出10且不崩潰”,是 未定義行為下的偶然結(jié)果:
- 堆內(nèi)存被釋放后未被復(fù)用,導(dǎo)致你能非法讀寫;
- 程序未觸發(fā)內(nèi)存保護(hù)機(jī)制,所以沒崩潰。
但這種代碼是“定時(shí)炸彈”——任何微小的改動(dòng)(比如增加函數(shù)調(diào)用、開啟優(yōu)化、多線程環(huán)境)都可能讓程序崩潰或邏輯錯(cuò)亂。永遠(yuǎn)不要依賴UB的表現(xiàn),遵守智能指針的使用規(guī)則,避免懸空指針。
三、問題
int* raw_ptr = nullptr; std::cout << *raw_ptr << std::endl;
1.上面這樣打印raw_ptr會(huì)程序崩潰,為什么
2.講下野指針
問題1:int* raw_ptr = nullptr; std::cout << *raw_ptr << std::endl; 為什么會(huì)崩潰?
核心原因:nullptr 指向的是“明確無效的內(nèi)存地址”,解引用會(huì)觸發(fā)操作系統(tǒng)的內(nèi)存保護(hù)機(jī)制,直接導(dǎo)致程序崩潰。
我們分3層講清楚:
nullptr的本質(zhì):nullptr是C++11引入的“空指針常量”,它不指向任何合法的內(nèi)存地址(通常對(duì)應(yīng)底層的0x0地址,或操作系統(tǒng)預(yù)留的“無效地址區(qū)間”)。
它和你之前代碼中“指向已釋放堆內(nèi)存的指針”完全不同:前者是“明確的無效地址”,后者是“曾經(jīng)有效、現(xiàn)在已被回收的地址”。解引用
nullptr的后果:操作系統(tǒng)會(huì)對(duì)內(nèi)存地址進(jìn)行權(quán)限管理——nullptr指向的地址是“程序絕對(duì)無權(quán)訪問”的(相當(dāng)于“不存在的地址”)。
當(dāng)你執(zhí)行*raw_ptr時(shí),程序試圖讀寫這個(gè)無效地址,會(huì)直接觸發(fā) 段錯(cuò)誤(SIGSEGV) 或“訪問違規(guī)”,操作系統(tǒng)會(huì)強(qiáng)制終止程序(也就是“崩潰”)。和你之前代碼的對(duì)比:
- 之前的
raw_ptr指向“已釋放但未復(fù)用的堆內(nèi)存”:地址本身是“合法存在”的,只是程序沒有使用權(quán),所以可能不崩潰(運(yùn)氣好); - 現(xiàn)在的
raw_ptr = nullptr:地址本身是“非法無效”的,程序一訪問就觸發(fā)內(nèi)存保護(hù),必然崩潰。
- 之前的
簡(jiǎn)單比喻:
- 之前的情況:你溜進(jìn)“已退租但未租給別人的房子”,可能沒被發(fā)現(xiàn);
- 現(xiàn)在的情況:你試圖闖進(jìn)“根本不存在的房子”(比如地址是0號(hào)),一靠近就被保安(操作系統(tǒng))攔下,直接“崩潰”。
問題2:野指針是什么?int* p; 是野指針嗎?
先明確:野指針的定義
野指針(Wild Pointer) 是指:指向“不確定地址”或“無效地址”,且沒有明確指向合法對(duì)象的指針。
核心特征:指針的值是“隨機(jī)的、未定義的”,或指向的內(nèi)存已被釋放/回收,訪問它的行為是“未定義行為(UB)”。
再回答:int* p;是野指針嗎?—— 是的,大概率是!
分兩種場(chǎng)景看 int* p; 的行為:
局部變量場(chǎng)景(最常見):如果
p是函數(shù)內(nèi)的局部變量(棧上分配),C++ 不會(huì)自動(dòng)初始化棧上的指針——p的值是棧上的“垃圾值”(隨機(jī)的二進(jìn)制數(shù)據(jù))。
這個(gè)隨機(jī)值可能指向:- 無效地址(比如
0x123456這種不存在的地址); - 其他程序的內(nèi)存地址(無權(quán)訪問);
- 本程序其他變量的地址(可能覆蓋其他數(shù)據(jù))。
無論哪種,p都是“指向不確定地址的指針”,屬于 典型的野指針。
- 無效地址(比如
全局/靜態(tài)變量場(chǎng)景:如果
p是全局變量或static變量,C++ 會(huì)默認(rèn)將其初始化為nullptr(或0),此時(shí)它是“空指針”,不是野指針(空指針是“明確的無效地址”,野指針是“不確定的無效地址”,兩者有區(qū)別)。
關(guān)鍵補(bǔ)充:野指針的3種常見來源
int* p;(未初始化的局部指針)只是野指針的一種,還有兩種更隱蔽的情況:
- 未初始化的局部指針(如
int* p;); - 指向已釋放內(nèi)存的指針(如你之前代碼中,
shared_ptr銷毀后的raw_ptr); - 指針指向的對(duì)象被
delete后,未置為nullptr(比如int* p = new int; delete p;,之后p還是指向原地址,變成野指針)。
容易混淆的概念:野指針 ≠ 空指針
| 類型 | 特點(diǎn) | 訪問后果 |
|---|---|---|
| 野指針 | 指針值隨機(jī)、不確定 | 可能崩潰、可能亂改數(shù)據(jù) |
空指針(nullptr) | 指針值明確(指向無效地址) | 幾乎必然崩潰(可預(yù)測(cè)UB) |
比如:
int* p;(局部變量)→ 野指針;int* p = nullptr;→ 空指針(不是野指針);int* p = new int; delete p;→ 野指針(指向已釋放地址)。
總結(jié)兩個(gè)問題的核心
- 解引用
nullptr必崩潰:因?yàn)樗赶?ldquo;明確無效的地址”,觸發(fā)內(nèi)存保護(hù); - 野指針是“指向不確定/無效地址的指針”:局部未初始化的
int* p;是野指針,空指針不是野指針; - 所有野指針的訪問都是UB,但后果不同(可能崩潰、可能亂碼、可能“看似正常”),但本質(zhì)都是極端危險(xiǎn)的。
補(bǔ)充:如何避免野指針?
- 指針聲明時(shí)立即初始化(要么指向合法對(duì)象,要么置為
nullptr):int* p = nullptr;或int a = 10; int* p = &a;; - 動(dòng)態(tài)內(nèi)存釋放后(
delete或shared_ptr銷毀),避免再使用對(duì)應(yīng)的指針; - 若需長期持有“可能失效的引用”,用
std::weak_ptr(而非裸指針); - 局部指針盡量縮短作用域,避免跨作用域傳遞裸指針。
總結(jié)
到此這篇關(guān)于c++中智能指針與裸指針的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)c++ 智能指針與裸指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中靜態(tài)庫與動(dòng)態(tài)庫的使用示例
在C/C++中使用庫的技術(shù),庫主要分為兩種類型:靜態(tài)庫和動(dòng)態(tài)庫,本文主要介紹了C++中靜態(tài)庫與動(dòng)態(tài)庫的使用示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
C++實(shí)現(xiàn)LeetCode(91.解碼方法)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(91.解碼方法),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
C++ OpenCV實(shí)戰(zhàn)之形狀識(shí)別
本案例通過使用OpenCV中的approxPolyDP進(jìn)行多邊形近似,進(jìn)而進(jìn)行基礎(chǔ)形狀識(shí)別(圓、三角形、矩形、星形…),快跟隨小編一起動(dòng)手嘗試一下2022-07-07
C++封裝紅黑樹以實(shí)現(xiàn)map和set方法詳解
這篇文章主要介紹了C++封裝紅黑樹以實(shí)現(xiàn)map和set的方法,本章從紅黑樹核心原理入手,封裝底層紅黑樹結(jié)構(gòu),復(fù)用實(shí)現(xiàn)map與set,掌握STL關(guān)聯(lián)容器底層實(shí)現(xiàn)與C++封裝思想2026-05-05
C++二叉搜索樹及其實(shí)現(xiàn)方法實(shí)例代碼
這篇文章主要介紹了C++二叉搜索樹及其實(shí)現(xiàn)方法的相關(guān)資料,搜索二叉樹是一種左小右大的二叉樹結(jié)構(gòu),支持高效查找、插入和刪除操作,需要的朋友可以參考下2025-06-06

