內存管理(智能指針,內存對齊,野指針,懸空指針)

📌 1. 野指針 (Wild Pointer)

什么是野指針?

野指針指的是未初始化的指針變量。它指向的內存地址是隨機的、未知的。

產生原因

cpp

int* ptr; // 野指針!未初始化,指向隨機地址
*ptr = 10; // 危險!可能破壞系統內存char* str; // 同樣是野指針
std::cout << *str; // 未定義行為

危險后果

  1. ** segmentation fault**:訪問受保護的內存區域

  2. 數據損壞:意外修改了其他程序或系統的數據

  3. 難以調試:錯誤表現隨機,難以重現

解決方法

總是初始化指針

cpp

int* ptr = nullptr; // 初始化為空指針
int* ptr2 = new int(10); // 初始化為有效內存
int* ptr3 = &someVariable; // 指向已有變量

📌 2. 懸空指針 (Dangling Pointer)

什么是懸空指針?

懸空指針指的是指針指向的內存已被釋放,但指針本身仍然保存著原來的地址。

產生原因

情況1:釋放后未置空

cpp

int* ptr = new int(10);
delete ptr; // 內存已釋放
// 現在ptr是懸空指針!
*ptr = 20; // 危險!訪問已釋放內存
情況2:局部變量返回

cpp

int* createArray() {int arr[5] = {1, 2, 3, 4, 5};return arr; // 返回局部數組的指針
} // arr的內存被釋放int* danglingPtr = createArray(); // 懸空指針!
std::cout << danglingPtr[0]; // 未定義行為
情況3:多個指針指向同一內存

cpp

int* ptr1 = new int(100);
int* ptr2 = ptr1; // 兩個指針指向同一內存delete ptr1; // 釋放內存
// 現在ptr1和ptr2都是懸空指針!
std::cout << *ptr2; // 危險!

危險后果

  1. ** use-after-free**:使用已釋放的內存

  2. 數據損壞:可能覆蓋新分配的內存數據

  3. 安全漏洞:可能被利用進行攻擊

解決方法

方法1:釋放后立即置空

cpp

int* ptr = new int(10);
delete ptr;
ptr = nullptr; // 重要!釋放后立即置空if (ptr != nullptr) { // 安全檢查*ptr = 20;
}
方法2:使用智能指針(推薦)

cpp

#include <memory>
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1; // 共享所有權// 不需要手動delete,自動管理生命周期
// 當所有shared_ptr都超出作用域時,內存自動釋放
方法3:避免返回局部變量地址

cpp

// 錯誤
int* createArray() {int arr[5] = {1, 2, 3, 4, 5};return arr; // 不要這樣做!
}// 正確:動態分配
int* createArray() {int* arr = new int[5]{1, 2, 3, 4, 5};return arr; // 調用者需要負責delete[]
}// 更正確:使用vector
std::vector<int> createArray() {return {1, 2, 3, 4, 5}; // 安全!
}

📌 一、什么是內存對齊?

內存對齊指的是數據在內存中的存儲地址必須是某個值(通常是2、4、8、16等2的冪次方)的整數倍。

簡單例子

cpp

struct Example {char a;      // 1字節int b;       // 4字節  short c;     // 2字節
};

沒有對齊時(假設從地址0開始):

text

地址: 0   1   2   3   4   5   6   7   8   9
數據: [a][b][b][b][b][c][c][ ][ ][ ]
總大小: 7字節?但實際上...

實際對齊后(在64位系統 typical alignment):

text

地址: 0   1   2   3   4   5   6   7   8   9   10  11
數據: [a][ ][ ][ ][b][b][b][b][c][c][ ][ ]
總大小: 12字節!(因為有填充字節)

📌 二、為什么需要內存對齊?

1.?硬件要求(最主要的原因)

現代CPU不是以字節為單位訪問內存,而是以字長(word size)為單位(通常為4字節或8字節)。

不對齊訪問的代價

cpp

// 假設int需要4字節對齊,但存儲在地址0x3
int* ptr = (int*)0x3; 
int value = *ptr; // CPU需要2次內存訪問!

CPU需要:

  1. 讀取地址0x0-0x3的4字節

  2. 讀取地址0x4-0x7的4字節

  3. 拼接出需要的4字節數據

2.?性能優化

對齊的內存訪問只需要1次內存操作,而不是2次或更多。

性能對比

訪問類型CPU操作次數性能影響
對齊訪問1次? 最快
不對齊訪問2次🐢 慢2倍
嚴重不對齊多次🚫 極慢

3.?平臺兼容性

某些架構(如ARM、SPARC)根本不允許未對齊的內存訪問,會導致硬件異常。

cpp

// 在ARM架構上可能直接崩潰!
int* misaligned_ptr = (int*)(char_buffer + 1);
int value = *misaligned_ptr; // 硬件異常!

📌 三、對齊規則和示例

基本對齊規則

每個數據類型都有自然的對齊要求:

  • char:1字節對齊

  • short:2字節對齊

  • int:4字節對齊

  • float:4字節對齊

  • double:8字節對齊

  • 指針:4字節(32位)或8字節(64位)對齊

結構體對齊示例

cpp

struct MyStruct {char a;      // 1字節,偏移0// 3字節填充(因為int需要4字節對齊)int b;       // 4字節,偏移4  short c;     // 2字節,偏移8// 2字節填充(使整體大小為最大成員的整數倍)
}; // 總大小:12字節

內存布局

text

偏移: 0   1   2   3   4   5   6   7   8   9   10  11
數據: [a][pad][pad][pad][b][b][b][b][c][c][pad][pad]

📌 四、如何控制內存對齊?

1. 編譯器指令(通用)

cpp

// 強制4字節對齊
struct alignas(4) MyStruct {char a;int b;short c;
}; // 大小:8字節(而不是12字節)// 或者使用pragma(編譯器特定)
#pragma pack(push, 1) // 強制1字節對齊(無填充)
struct TightPacked {char a;int b;  short c;
}; // 大小:7字節
#pragma pack(pop) // 恢復默認對齊

2. C++11 alignas 關鍵字

cpp

#include <iostream>struct alignas(16) AlignedStruct {int a;double b;char c;
};int main() {std::cout << "Alignment: " << alignof(AlignedStruct) << std::endl;std::cout << "Size: " << sizeof(AlignedStruct) << std::endl;return 0;
}

3. 動態內存對齊

cpp

#include <cstdlib>
#include <iostream>// C11/C++17 的動態對齊分配
void* aligned_memory = std::aligned_alloc(64, 1024); // 64字節對齊,分配1KB
// 使用...
std::free(aligned_memory);

在 C++ 中,智能指針是一種封裝了原始指針的類模板,用于自動管理動態內存,避免內存泄漏。它們通過 RAII(資源獲取即初始化)機制,在離開作用域時自動釋放所指向的內存。C++ 標準庫提供了三種主要的智能指針:unique_ptrshared_ptr?和?weak_ptr,它們各自有不同的特性和用途。

1.?unique_ptr:獨占所有權的智能指針

  • 特性:同一時間內,只能有一個?unique_ptr?指向某塊內存,即所有權是獨占的。
  • 行為
    • 不允許拷貝(copy),但允許移動(move),即所有權可以轉移。
    • 當?unique_ptr?離開作用域或被銷毀時,會自動釋放所指向的內存。
  • 適用場景
    • 管理單個對象的動態內存,且不需要共享所有權。
    • 作為函數的返回值或參數(通過移動語義傳遞)。
  • 示例

    cpp

    運行

    #include <memory>
    int main() {std::unique_ptr<int> ptr1(new int(10));  // 獨占指向10的內存// std::unique_ptr<int> ptr2 = ptr1;  // 錯誤:不能拷貝std::unique_ptr<int> ptr2 = std::move(ptr1);  // 正確:轉移所有權(ptr1變為空)return 0;
    }  // ptr2離開作用域,自動釋放內存
    

2.?shared_ptr:共享所有權的智能指針

  • 特性:允許多個?shared_ptr?指向同一塊內存,通過引用計數跟蹤所有者數量。
  • 行為
    • 當一個?shared_ptr?被拷貝時,引用計數加 1;當被銷毀時,引用計數減 1。
    • 當引用計數減為 0 時,自動釋放所指向的內存。
  • 適用場景
    • 需要多個對象共享同一資源的所有權(例如:樹結構中父節點和子節點互相引用)。
  • 注意
    • 避免循環引用(如兩個?shared_ptr?互相指向對方),會導致引用計數無法歸零,內存泄漏。此時需配合?weak_ptr?解決。
  • 示例

    cpp

    運行

    #include <memory>
    int main() {std::shared_ptr<int> ptr1(new int(20));std::shared_ptr<int> ptr2 = ptr1;  // 引用計數變為2{std::shared_ptr<int> ptr3 = ptr1;  // 引用計數變為3}  // ptr3銷毀,引用計數變為2return 0;
    }  // ptr1和ptr2銷毀,引用計數變為0,內存釋放
    

3.?weak_ptr:弱引用的智能指針

  • 特性:一種 “弱引用”,不擁有所指向內存的所有權,也不影響引用計數。
  • 行為
    • 必須從?shared_ptr?轉換而來,無法直接管理內存。
    • 可以通過?lock()?方法獲取一個?shared_ptr(若內存未釋放),否則返回空。
  • 適用場景
    • 解決?shared_ptr?的循環引用問題。
    • 需訪問某資源,但不希望延長其生命周期(例如:緩存、觀察者模式)。
  • 示例

    cpp

    運行

    #include <memory>
    struct Node {std::weak_ptr<Node> parent;  // 用weak_ptr避免循環引用// std::shared_ptr<Node> parent;  // 若用shared_ptr,會導致循環引用
    };int main() {std::shared_ptr<Node> child(new Node());std::shared_ptr<Node> parent(new Node());child->parent = parent;  // weak_ptr不增加引用計數return 0;
    }  // 引用計數正常歸零,內存釋放
    

三者的核心區別總結

智能指針所有權拷貝 / 移動引用計數主要用途
unique_ptr獨占禁止拷貝,允許移動管理單個對象,不共享所有權
shared_ptr共享允許拷貝和移動多對象共享同一資源
weak_ptr無所有權允許拷貝和移動不影響解決循環引用,弱引用資源

通過合理使用這三種智能指針,可以大幅減少手動管理內存的錯誤,使 C++ 代碼更安全、更易維護。

在 C++ 中,循環引用(Circular Reference)?是使用shared_ptr時容易出現的問題,它會導致內存泄漏。這一問題的根源在于shared_ptr的引用計數機制與循環依賴的結合,使得引用計數無法歸零,最終導致動態內存無法釋放。

什么是循環引用?

當兩個或多個shared_ptr互相持有對方的引用,形成一個 “閉環” 時,就會發生循環引用。此時,每個shared_ptr的引用計數都無法減到 0,導致它們指向的內存永遠不會被釋放,造成內存泄漏。

舉個具體例子

假設我們有兩個類AB,它們互相用shared_ptr引用對方:

cpp

運行

#include <memory>class B;  // 前向聲明class A {
public:std::shared_ptr<B> b_ptr;  // A持有B的shared_ptr~A() { std::cout << "A被銷毀" << std::endl; }
};class B {
public:std::shared_ptr<A> a_ptr;  // B持有A的shared_ptr~B() { std::cout << "B被銷毀" << std::endl; }
};int main() {{std::shared_ptr<A> a(new A());std::shared_ptr<B> b(new B());// 形成循環引用:a持有b,b持有aa->b_ptr = b;b->a_ptr = a;}  // 離開作用域,預期A和B被銷毀return 0;
}

運行結果:程序不會輸出 “A 被銷毀” 和 “B 被銷毀”,說明AB的內存沒有被釋放,發生了內存泄漏。

為什么會發生內存泄漏?

我們一步步分析引用計數的變化:

  1. 創建a(指向 A 對象)時,A 的引用計數為 1。
  2. 創建b(指向 B 對象)時,B 的引用計數為 1。
  3. a->b_ptr = b:B 的引用計數變為 2(ba->b_ptr共同引用)。
  4. b->a_ptr = a:A 的引用計數變為 2(ab->a_ptr共同引用)。
  5. 離開作用域時,ab被銷毀:
    • a銷毀:A 的引用計數從 2 減為 1(剩余b->a_ptr的引用)。
    • b銷毀:B 的引用計數從 2 減為 1(剩余a->b_ptr的引用)。
  6. 此時,A 和 B 的引用計數都為 1,永遠不會再減為 0,它們的內存永遠不會被釋放。

如何解決循環引用?

使用weak_ptr可以打破循環引用。weak_ptr是一種 “弱引用”,它持有對對象的引用,但不增加引用計數,因此不會影響對象的生命周期。

修改上面的例子,將其中一個shared_ptr改為weak_ptr

cpp

運行

#include <memory>
#include <iostream>class B;class A {
public:std::shared_ptr<B> b_ptr;  // 仍用shared_ptr~A() { std::cout << "A被銷毀" << std::endl; }
};class B {
public:std::weak_ptr<A> a_ptr;  // 改為weak_ptr,不增加引用計數~B() { std::cout << "B被銷毀" << std::endl; }
};int main() {{std::shared_ptr<A> a(new A());std::shared_ptr<B> b(new B());a->b_ptr = b;  // B的引用計數變為2b->a_ptr = a;  // A的引用計數仍為1(weak_ptr不增加計數)}  // 離開作用域return 0;
}

運行結果:輸出 “A 被銷毀” 和 “B 被銷毀”,內存正常釋放。

原因分析

  1. b->a_ptrweak_ptr,賦值時 A 的引用計數仍為 1(不增加)。
  2. 離開作用域時,a銷毀:A 的引用計數從 1 減為 0 → A 被釋放。
  3. A 釋放后,其成員b_ptr(指向 B)被銷毀:B 的引用計數從 2 減為 1。
  4. 隨后b銷毀:B 的引用計數從 1 減為 0 → B 被釋放。

總結

  • 循環引用shared_ptr的常見陷阱,由互相引用的shared_ptr形成閉環導致。
  • 核心問題:引用計數無法歸零,內存無法釋放。
  • 解決方案:在循環引用的一方使用weak_ptr,打破計數閉環。
  • weak_ptr的適用場景:需要引用對象,但不希望影響其生命周期(如解決循環引用、緩存等)。

通過合理搭配shared_ptrweak_ptr,可以既享受共享所有權的便利,又避免循環引用帶來的內存泄漏。

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

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

相關文章

Spring代理的特點

一.Spring代理的特點1.依賴注入和初始化影響的是原始的對象。2.代理和目標是兩個對象&#xff0c;二者成員變量不共用數據。二.測試首先準備以下幾個類。Bean1package com.example.springdemo.demos.a13;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.…

嵌入式學習日記(35)TCP并發服務器構建

單循環服務器&#xff1a;服務端同一時刻只能處理一個客戶端的任務并發服務器&#xff1a;服務端同一時刻可以處理多個客戶端的任務TCP并發服務器構建&#xff1a;TCP&#xff1a; 1. 建立連接&#xff0c;一對一TCP服務端并發模型&#xff1a;1. 多進程進程資源開銷大&#x…

【Flask】測試平臺開發實戰-第二篇

概述&#xff1a;在上一篇我們已經將后端初始化服務后前端的vue框架初始化已經搭建完整&#xff0c;并且可以運行看到界面&#xff0c;服務正常&#xff0c;當然我們看到的前端數據都是mock下的假數據&#xff0c;并不是真實的&#xff0c;這篇我們來開發實現第一個功能&#x…

聊一聊 .NET 的 AssemblyLoadContext 可插拔程序集

一&#xff1a;背景 1. 講故事 最近在分析一個崩潰dump時&#xff0c;發現禍首和AssemblyLoadContext有關&#xff0c;說實話這東西我也比較陌生&#xff0c;后來查了下大模型&#xff0c;它主要奔著替代 .NetFrameWork 時代的 AppDomain 的&#xff0c;都是用來做晚期加卸載&a…

Java中使用Spring Boot+Ollama實現本地AI的MCP接入

目錄結構完善spring bootpom.xml添加依賴application.ymlMCP 工具配置 mcp-servers.json配置類編寫API在我的上一篇文章搭建好本地的聊天機器人后&#xff0c;準備接入MCP進一步增強AI的能力&#xff0c;以實現類似手機AI的功能 參考的是第二篇文章鏈接其內容比較精煉&#x…

C#正則表達式與用法

&#x1f31f; C# 常用正則表達式與用法C# 使用正則需要引用命名空間&#xff1a;using System.Text.RegularExpressions; 常用方法&#xff1a;Regex.IsMatch(input, pattern) → 返回 bool&#xff0c;用于驗證Regex.Match(input, pattern) → 返回 Match 對象&#xff0c;可…

從0開始學習Java+AI知識點總結-27.web實戰(Maven高級)

一、分模塊設計與開發&#xff1a;讓項目結構更清晰1.1 為什么需要分模塊&#xff1f;單模塊開發的痛點在小型項目中&#xff0c;單模塊&#xff08;所有代碼放在一個工程&#xff09;或許能滿足需求&#xff0c;但項目規模擴大后會出現兩大核心問題&#xff1a;維護成本高&…

Ferris Wheel (貪心 | 雙指針)

題目&#xff1a;思路&#xff1a;本題注意題目的條件即可&#xff0c;題意說一個摩天輪可以坐一個人或者兩個人&#xff0c;那么顯然我們就可以貪心一下具體的&#xff0c;我們可以讓最小的去匹配最大的&#xff0c;如果此時大于 x&#xff0c;那么顯然我們根本無法使得 最大的…

課程視頻怎么加密?在線教育機構常用的6個課程加密方法

知識付費時代&#xff0c;課程視頻是教育機構的核心資產。但是不難發現&#xff0c;課程視頻的安全卻得不到保障。各大購物平臺搜索課程名稱&#xff0c;便出現了許多盜版課程。如何有效防止課程被翻錄和二次傳播&#xff0c;成為急需解決的關鍵問題。今天這期分享點干貨&#…

SOME/IP-SD中”服務器服務組播端點”、“客戶端服務組播端點”與“IPv4組播選項的區分

<摘要> AUTOSIP-SD協議中組播端點&#xff08;Multicast Endpoint&#xff09;在不同上下文中的角色與表述差異。準確理解“服務器服務組播端點”、“客戶端服務組播端點”與“IPv4組播選項”中配置的端點之間的關系&#xff0c;是正確實現組播事件分發機制的關鍵。這涉及…

計算機是如何運行的

目錄 一&#xff0c;計算機是如何組成的 1.1&#xff0c;CPU中央處理單元 1.1.1&#xff0c;CPU的構成和屬性 1.1.2&#xff0c;如何判斷cpu的好壞 1.1.3&#xff0c;指令 1.1.4&#xff0c;CPU的緩存 1.2&#xff0c;操作系統 1.2.1&#xff0c;進程 1.2.2&#xff0…

JavaScript性能優化:實戰技巧與高效策略

JavaScript性能優化實戰技術文章大綱性能優化的重要性解釋為什么性能優化對用戶體驗和業務指標至關重要列舉常見性能問題的影響&#xff08;如跳出率、轉化率下降&#xff09;代碼層面的優化減少全局變量使用&#xff0c;避免內存泄漏使用事件委托減少事件監聽器的數量避免頻繁…

解決.env.production 寫死 IP 的問題:Vue + config.json 運行時加載方案

背景&#xff1a;前端常用 .env.production 在構建時寫死 API 地址 場景&#xff1a;運維部署時經常不知道目標主機 IP/域名 問題&#xff1a;每次 IP 變動都要重新編譯 → 增加運維成本 引出需求&#xff1a;只修改 IP 就能完成部署&#xff0c;不需要重新打包 目錄一、解決方…

如何從三星手機轉移到另一部三星手機

三星Galaxy S系列因其出色的設計、令人驚嘆的顯示屏、驚艷的攝像頭、更好的揚聲器以及創新的指紋傳感器而受到大多數用戶的歡迎&#xff0c;獲得了良好的聲譽。讓用戶感到滿意的是&#xff0c;三星Galaxy S10擁有更美觀的設計、令人驚嘆的顯示屏、令人驚嘆的攝像頭、更好的揚聲…

聚焦建筑能源革新!安科瑞 “光儲直柔” 方案護航碳中和目標實現

1、背景在 “雙碳” 目標引領下&#xff0c;能源結構轉型與建筑能效提升成為重要課題。清華大學江億院士提出的 “光儲直柔” 新型配電系統&#xff0c;為建筑領域綠色發展提供了創新方向。光儲直柔得到了業界廣泛認同和積極響應&#xff0c;國家、各部委、地區陸續出臺相關政策…

Shell 中 ()、(())、[]、{} 的用法詳解

文章目錄Shell 中 ()、(())、[]、{} 的用法詳解一、先明確&#xff1a;四類符號的核心功能定位二、逐個拆解&#xff1a;用法、示例與避坑點1. ()&#xff1a;子 Shell 執行&#xff0c;隔離環境核心用法1&#xff1a;子 Shell 執行命令&#xff0c;隔離變量核心用法2&#xff…

開發避坑指南(41):Vue3 提示框proxy.$modal.msgSuccess()提示文本換行解決方案

需求 由于接口返回的提示信息過長&#xff0c;接口已經在返回提示中加入換行標簽了&#xff0c;但是使用proxy.modal.msgSuccess(res.msg)提示沒有換行&#xff0c;那么Vue3中proxy.modal.msgSuccess(res.msg)提示沒有換行&#xff0c;那么Vue3 中 proxy.modal.msgSuccess(res.…

[Sync_ai_vid] 唇形同步推理流程 | Whisper架構

鏈接&#xff1a;https://github.com/bytedance/LatentSync/blob/main/docs/syncnet_arch.md docs&#xff1a;LatentSync LatentSync是一個端到端唇語同步項目&#xff0c;能夠生成語音與唇形完美匹配的逼真視頻。 該項目通過使用*音頻條件化3D U-Net*&#xff08;一種生成式…

uniapp中 ios端 scroll-view 組件內部子元素z-index失效問題

發現子組件中的彈窗在ios手機上會被限制在scroll-view里面&#xff0c;安卓手機上不受限制&#xff0c;網上找了好久原因 scroll-view組件內部設置了 -webkit-overflow-scrolling: touch 樣式&#xff0c;導致z-index失效&#xff08;safari 3D變換會忽略z-index的層級問題&…

PyTorch圖像預處理完全指南:從基礎操作到GPU加速實戰

引言 圖像預處理是模型性能的"隱形基石"&#xff0c;在計算機視覺任務中直接決定模型能否提取有效特征。科學的預處理流程能讓基礎模型性能提升15%以上&#xff0c;而GPU加速預處理可使數據準備階段耗時降低60%以上。本文將聚焦PyTorch預處理核心技術&#xff0c;從基…