C++系列(七):深度探索C++內存 --- 分區、堆棧、new/delete與高效編程實踐

引言

程序運行的本質是對數據的處理,而內存則是程序執行的核心舞臺。理解內存的物理與邏輯分區,是掌握程序底層行為、編寫高效可靠代碼的關鍵基石。內存并非混沌一片,而是被嚴格劃分為代碼區、全局區、棧區和堆區。每個區域擁有獨特的生命周期、訪問規則和管理機制:代碼區存放不變的指令,全局區承載靜態數據,棧區高效管理局部變量與調用,堆區則提供靈活的動態內存空間。newdelete是C++直接操控堆內存的核心工具,其正確使用直接影響程序的健壯性。深入剖析這些分區的工作原理及動態內存管理策略(如智能指針、內存池),并掌握內存泄漏檢測與優化技巧,是構建高性能、高穩定性系統不可或缺的能力。本內容將系統解析這些底層機制,為高級內存管理實踐奠定堅實基礎。

最后,如果大家喜歡我的創作風格,請大家多多關注up主,你們的支持就是我創作最大的動力!如果各位觀眾老爺覺得我哪些地方需要改進,請一定在評論區告訴我,馬上改!在此感謝大家了。

各位觀眾老爺,本文通俗易懂,快速熟悉C++,收藏本文,關注up不迷路,后續將持續分享C++純干貨(請觀眾老爺放心,絕對又干又通俗易懂)。請多多關注、收藏、評論,評論區等你~~~



文章目錄

  • 引言
  • 一、內存分區全景圖
    • 1.1 四大內存區域詳解
    • 1.2 內存布局示意圖
  • 二、程序運行前:代碼區與全局區
    • 2.1 代碼區深入剖析
    • 2.2 全局區深度探索
  • 三、程序運行后:棧區與堆區
    • 3.1 棧區工作機制詳解
    • 3.2 堆區動態管理實戰
  • 四、new與delete深度探索
    • 4.1 new的多種用法
    • 4.2 delete的進階技巧
  • 五、內存管理最佳實踐與高級技巧
    • 5.1 智能指針實戰
    • 5.2 內存池技術
    • 5.3 內存泄漏檢測技術
  • 六、綜合應用:高性能內存管理
    • 6.1 自定義分配器
    • 6.2 內存優化策略



正 文

一、內存分區全景圖

1.1 四大內存區域詳解

C++程序在執行時,將內存劃分為4個主要區域

內存區域管理方式存放內容生命周期特點
代碼區操作系統函數體的二進制代碼程序整個運行期共享、只讀、穩定
全局區操作系統全局變量、靜態變量、常量程序整個運行期數據持久化、可被所有函數訪問
棧區編譯器函數參數、局部變量函數執行期間自動管理、空間有限、高效
堆區程序員動態分配的數據顯式釋放或程序結束空間大、靈活控制、需手動管理

內存分區意義: 不同區域存放的數據具有不同的生命周期,為編程提供了更大的靈活性,使我們能夠更有效地管理內存資源。

1.2 內存布局示意圖

高地址
┌─────────────┐
│   棧區      │ ← 向下增長
├─────────────┤
│             │
│   堆區      │ ← 向上增長
├─────────────┤
│   全局區    │
│  ┌─────────┐│
│  │ .data   ││ → 已初始化全局/靜態變量
│  ├─────────┤│
│  │ .bss    ││ → 未初始化全局/靜態變量
│  ├─────────┤│
│  │ 常量區  ││ → 字符串常量、全局常量
│  └─────────┘│
├─────────────┤
│   代碼區    │
└─────────────┘
低地址

二、程序運行前:代碼區與全局區

2.1 代碼區深入剖析

  • 存放內容: CPU執行的機器指令;

  • 主要特性:

    • 共享性: 頻繁執行的程序只需在內存中保留一份代碼;

    • 只讀性: 防止程序意外修改指令;

    • 穩定性: 代碼在程序運行期間不會改變;

  • 特性驗證示例

#include <iostream>void func1() { std::cout << "Function 1\n"; }
void func2() { std::cout << "Function 2\n"; }int main() {// 驗證函數地址(代碼區)std::cout << "func1地址: " << (void*)func1 << std::endl;std::cout << "func2地址: " << (void*)func2 << std::endl;// 嘗試修改代碼區(將導致段錯誤)// char* p = (char*)func1;// *p = 0xC3; // 嘗試寫入RET指令return 0;
}

2.2 全局區深度探索

  • 存放內容:

    • 全局變量和靜態變量

    • 常量(字符串常量、const修飾的全局常量)

生命周期:程序結束后由操作系統釋放

數據特性:該區域的數據在程序整個運行期間都存在

全局區結構驗證

#include <iostream>// .data段:已初始化全局變量
int g_data = 100;// .bss段:未初始化全局變量
int g_bss;// 常量區:全局常量
const int g_const = 200;
const char* g_str = "Global String";int main() {// 已初始化靜態變量(.data)static int s_data = 300;// 未初始化靜態變量(.bss)static int s_bss;std::cout << ".data段變量地址:\n";std::cout << "g_data: " << &g_data << "\ns_data: " << &s_data << "\n\n";std::cout << ".bss段變量地址:\n";std::cout << "g_bss: " << &g_bss << "\ns_bss: " << &s_bss << "\n\n";std::cout << "常量區地址:\n";std::cout << "g_const: " << &g_const << "\n";std::cout << "g_str: " << (void*)g_str << "\n";std::cout << "Literal: " << (void*)"Hello World" << "\n";// 局部變量對比(棧區)int local = 400;const int local_const = 500;std::cout << "\n棧區地址:\n";std::cout << "local: " << &local << "\n";std::cout << "local_const: " << &local_const << "\n";return 0;
}

關鍵發現

  1. 已初始化全局/靜態變量相鄰(.data段)
  2. 未初始化全局/靜態變量相鄰(.bss段)
  3. 常量區地址明顯低于棧區地址
  4. 相同字符串常量共享同一內存地址

三、程序運行后:棧區與堆區

3.1 棧區工作機制詳解

1. 棧幀結構與函數調用

#include <iostream>void inner(int x) {int a = x * 2;std::cout << "Inner棧幀:\n";std::cout << "  a: " << &a << "\n";
}void outer(int y) {int b = y + 5;std::cout << "Outer棧幀:\n";std::cout << "  b: " << &b << "\n";inner(b);
}int main() {int num = 10;std::cout << "Main棧幀:\n";std::cout << "  num: " << &num << "\n";outer(num);return 0;
}

2. 輸出分析

Main棧幀:num: 0x7ffd4d4a5a4c
Outer棧幀:b: 0x7ffd4d4a5a2c
Inner棧幀:a: 0x7ffd4d4a5a0c

地址遞減趨勢清晰展示了棧的增長方向(高地址→低地址)

3.2 堆區動態管理實戰

#include <iostream>int main() {// 單變量動態分配int* pNum = new int(25);std::cout << "堆整數: " << *pNum << " at " << pNum << "\n";// 數組動態分配const int SIZE = 5;double* arr = new double[SIZE]{1.1, 2.2, 3.3, 4.4, 5.5};std::cout << "堆數組: ";for (int i = 0; i < SIZE; ++i) {std::cout << arr[i] << " ";}std::cout << "at " << arr << "\n";// 正確釋放內存delete pNum;delete[] arr;// 避免懸空指針pNum = nullptr;arr = nullptr;return 0;
}

四、new與delete深度探索

4.1 new的多種用法

#include <iostream>
#include <new> // 包含bad_alloc和nothrowint main() {// 1. 基本newint* p1 = new int(10);// 2. 數組newint* arr = new int[5]{1, 2, 3, 4, 5};// 3. 異常處理版try {int* big = new int[1000000000000];} catch (const std::bad_alloc& e) {std::cerr << "內存分配失敗: " << e.what() << "\n";}// 4. 無異常版(返回nullptr)int* safe = new(std::nothrow) int[1000000000000];if (!safe) {std::cerr << "安全分配失敗\n";}// 5. 定位new(在現有內存上構造)char buffer[1024];int* p2 = new (buffer) int(20);std::cout << "定位new值: " << *p2 << " at " << p2 << "\n";// 清理delete p1;delete[] arr;return 0;
}

4.2 delete的進階技巧

#include <iostream>class Resource {
public:Resource() { std::cout << "資源獲取\n"; }~Resource() { std::cout << "資源釋放\n"; }
};int main() {// 1. 基本deleteResource* res = new Resource();delete res;// 2. 數組deleteResource* arr = new Resource[3];delete[] arr; // 調用3次析構函數// 3. 虛析構函數的重要性class Base {public:virtual ~Base() { std::cout << "Base析構\n"; }};class Derived : public Base {public:~Derived() override { std::cout << "Derived析構\n"; }};Base* poly = new Derived();delete poly; // 正確調用Derived析構函數// 4. 刪除void指針的問題void* pvoid = new int(30);// delete pvoid; // 未定義行為 - 不知道要調用什么析構函數delete static_cast<int*>(pvoid); // 正確方式return 0;
}

五、內存管理最佳實踐與高級技巧

5.1 智能指針實戰

#include <iostream>
#include <memory>
#include <vector>class Widget {
public:Widget() { std::cout << "Widget創建\n"; }~Widget() { std::cout << "Widget銷毀\n"; }void process() { std::cout << "處理Widget\n"; }
};int main() {// 1. unique_ptr(獨占所有權)std::unique_ptr<Widget> uptr = std::make_unique<Widget>();uptr->process();// 2. shared_ptr(共享所有權)std::shared_ptr<Widget> sptr1 = std::make_shared<Widget>();{std::shared_ptr<Widget> sptr2 = sptr1;std::cout << "引用計數: " << sptr1.use_count() << "\n";}std::cout << "引用計數: " << sptr1.use_count() << "\n";// 3. weak_ptr(打破循環引用)struct Node {std::shared_ptr<Node> next;std::weak_ptr<Node> prev; // 使用weak_ptr避免循環引用~Node() { std::cout << "節點銷毀\n"; }};auto node1 = std::make_shared<Node>();auto node2 = std::make_shared<Node>();node1->next = node2;node2->prev = node1;// 4. 智能指針數組(C++17)auto arr = std::make_unique<int[]>(5);for (int i = 0; i < 5; ++i) {arr[i] = i * 10;}return 0;
}

5.2 內存池技術

#include <iostream>
#include <vector>// 簡易內存池實現
class MemoryPool {
public:MemoryPool(size_t blockSize, size_t blockCount): blockSize(blockSize){// 分配大塊內存pool = new char[blockSize * blockCount];// 初始化空閑列表for (size_t i = 0; i < blockCount; ++i) {freeList.push_back(pool + i * blockSize);}}~MemoryPool() {delete[] pool;}void* allocate() {if (freeList.empty()) {throw std::bad_alloc();}void* block = freeList.back();freeList.pop_back();return block;}void deallocate(void* block) {freeList.push_back(static_cast<char*>(block));}private:size_t blockSize;char* pool;std::vector<void*> freeList;
};// 使用內存池的類
class PooledObject {
public:static void* operator new(size_t size) {return pool.allocate();}static void operator delete(void* ptr) {pool.deallocate(ptr);}private:static MemoryPool pool;double data[1024]; // 大數據成員
};// 初始化內存池(每個對象8KB,最多100個)
MemoryPool PooledObject::pool(sizeof(PooledObject), 100);int main() {// 使用自定義內存管理PooledObject* obj1 = new PooledObject();PooledObject* obj2 = new PooledObject();delete obj1;delete obj2;return 0;
}

5.3 內存泄漏檢測技術

#include <iostream>
#include <cstdlib>// 重載全局new/delete以跟蹤分配
void* operator new(size_t size) {void* ptr = malloc(size);std::cout << "分配 " << size << " 字節 at " << ptr << "\n";return ptr;
}void operator delete(void* ptr) noexcept {std::cout << "釋放內存 at " << ptr << "\n";free(ptr);
}void operator delete[](void* ptr) noexcept {std::cout << "釋放數組 at " << ptr << "\n";free(ptr);
}class Leaky {
public:Leaky() { data = new int[10]; }~Leaky() { } // 故意不刪除data
private:int* data;
};int main() {// 正常使用int* p = new int(42);delete p;// 內存泄漏示例Leaky* leaky = new Leaky();delete leaky; // 只刪除了對象,內部data泄漏// 數組泄漏double* arr = new double[100];// 忘記delete[]return 0;
}

六、綜合應用:高性能內存管理

6.1 自定義分配器

#include <iostream>
#include <vector>
#include <memory>// 棧分配器:從預分配緩沖區分配內存
template <typename T>
class StackAllocator {
public:using value_type = T;StackAllocator(char* buffer, size_t size): buffer(buffer), size(size), offset(0) {}template <typename U>StackAllocator(const StackAllocator<U>& other): buffer(other.buffer), size(other.size), offset(other.offset) {}T* allocate(size_t n) {if (offset + n * sizeof(T) > size) {throw std::bad_alloc();}T* ptr = reinterpret_cast<T*>(buffer + offset);offset += n * sizeof(T);return ptr;}void deallocate(T*, size_t) noexcept {// 棧分配器不實際釋放內存}private:char* buffer;size_t size;size_t offset;
};int main() {// 預分配1MB緩沖區const size_t BUFFER_SIZE = 1024 * 1024;char buffer[BUFFER_SIZE];// 使用自定義分配器的vectorusing StackVector = std::vector<int, StackAllocator<int>>;StackAllocator<int> alloc(buffer, BUFFER_SIZE);StackVector vec(alloc);for (int i = 0; i < 1000; ++i) {vec.push_back(i);}std::cout << "使用棧分配器的vector大小: " << vec.size() << "\n";return 0;
}

6.2 內存優化策略

  1. 對象池模式:對頻繁創建銷毀的對象使用對象池
  2. 小內存分配優化:使用slab分配器管理小對象
  3. 內存對齊:使用alignas確保關鍵數據結構對齊
    struct alignas(64) CacheLineAligned {int data[16];
    };
    
  4. 寫時復制(Copy-on-Write):減少不必要的內存拷貝
  5. 內存映射文件:處理超大文件
    #include <sys/mman.h>
    // 將文件映射到內存
    void* mapped = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    


結 束 語

能夠看到這里的觀眾老爺,無疑是對up的最大肯定和支持,在此懇求各位觀眾老爺能夠多多點贊、收藏和關注。在這個合集中,未來將持續給大家分享關于C++的多種常見開發實用操作。未來也將繼續分享各種實用干貨。感謝大家支持!



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

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

相關文章

微信小程序71~80

1.總結小程序生命周期 小程序冷啟動&#xff0c;鉤子函數執行的順序保留當前頁面&#xff0c;進入下一個頁面&#xff0c;鉤子函數執行的順序銷毀當前頁面&#xff0c;進入下一個頁面&#xff0c;鉤子函數執行的順序小程序熱啟動&#xff0c;鉤子函數執行的順序 2.使用Componen…

[Pytest][Part 3]檢測python package狀態

目錄 實現需求1&#xff1a; 檢查python package狀態——pkg_resource hook實現自動檢測包狀態 conftest.py hook鉤子函數 Part1: https://blog.csdn.net/x1987200567/article/details/144915315?spm1001.2014.3001.5501 從這里開始逐個實現Part1中的需求 實現需求1&a…

自定義時間范圍選擇組件使用教程(基于 Vue 3 + Element Plus)

&#x1f553; 自定義時間范圍選擇組件使用教程&#xff08;基于 Vue 3 Element Plus&#xff09;? 一個靈活實用的時間范圍選擇器&#xff0c;支持開始時間、結束時間、快捷時間選項、本地雙向綁定、插槽擴展等功能。–&#x1f4d8; 一、功能介紹 該組件基于 Element Plus …

YOLOv8 模型轉換 ONNX 后 C# 調用異常:一個參數引發的跨平臺適配難題

一、問題背景&#xff1a;從 Python 訓練到 C# 部署的跨平臺需求 作為一名 C# 開發者&#xff0c;我在完成 YOLOv8 模型訓練&#xff08;使用 Ultralytics 官方框架&#xff0c;訓練數據為自定義目標檢測數據集&#xff0c;輸入尺寸 640x640&#xff0c;訓練輪次 100 輪&#…

Apache Cloudberry 亮相 2025 IvorySQL 生態大會暨 PostgreSQL 高峰論壇

6 月 27 日至 28 日&#xff0c;IvorySQL 2025 生態大會暨 PostgreSQL 高峰論壇在泉城濟南順利召開。本屆大會由 IvorySQL 開源數據庫社區主辦、瀚高基礎軟件股份有限公司承辦&#xff0c;吸引了來自國內外的數據庫技術專家、開發者與開源愛好者齊聚一堂&#xff0c;聚焦數據庫…

CMake之CMakeLists.txt語法規則

本文主要參考正點原子的應用開發手冊&#xff0c;僅作為本人學習筆記使用。 目錄 cmake 的使用方法其實還是非常簡單的&#xff0c;重點在于編寫 CMakeLists.txt&#xff0c;CMakeLists.txt 的語法規則也簡單&#xff0c;并沒有 Makefile的語法規則那么復雜難以理解&#xff01…

Mysql專題復習

重點內容&#xff1a;1. Mysql架構&#xff1a;客戶端 Server層 存儲引擎2. 索引數據結構&#xff1a;B樹4. 索引優化&#xff1a;覆蓋索引、排序、JOIN、分頁&#xff1b; COUNT; 索引下推&#xff1b;單/雙路排序5. 數據庫事務&#xff1b; 鎖&#xff1b;隔離級別&#xff…

CLIP的tokenizer詳解

一、bytes_to_unicodedef bytes_to_unicode():"""Returns list of utf-8 byte and a corresponding list of unicode strings.The reversible bpe codes work on unicode strings.This means you need a large # of unicode characters in your vocab if you wa…

【如何判斷Linux系統是Ubuntu還是CentOS】

要確定您的操作系統是 Ubuntu 還是 CentOS&#xff0c;可以通過以下方法快速檢查&#xff1a; 方法 1&#xff1a;通過終端命令&#xff08;推薦&#xff09; 在終端中執行以下命令之一&#xff1a; 查看 /etc/os-release 文件 cat /etc/os-releaseUbuntu 特征&#xff1a;顯示…

RISCV Linux 虛擬內存精講系列二 -- Linux 入口 head.S

通過 Linux 的構建系統&#xff0c;即 Linux 源代碼的根目錄下的 Makefile&#xff0c;能夠找到 vmlinux 的鏈接文件&#xff0c;從而能夠查看其入口代碼 head.S:_start&#xff0c; 如下&#xff1a; Linux 構建系統主Makefile: vmlinux.lds: head.S: 找到該入口后&#xff0c…

springAI學習:Advisors

spring AI Advisors類似于攔截器&#xff0c;會對請求的prompt做出特定的修改和增強&#xff08;比如傳入歷史溝通記錄、搜索信息等等&#xff09;&#xff0c;以達到完善prompt的目的。通過Advisors API&#xff0c;開發人員可以創建更為復雜、可重用、可維護的AI組件。下面介…

MySQL CDC與Kafka整合指南:構建實時數據管道的完整方案

一、引言&#xff1a;現代數據架構的實時化需求 在數字化轉型浪潮中&#xff0c;實時數據已成為企業的核心資產。傳統批處理ETL&#xff08;每天T1&#xff09;已無法滿足以下場景需求&#xff1a; 實時風險監控&#xff08;金融交易&#xff09;即時個性化推薦&#xff08;電商…

MATLAB | 繪圖復刻(二十一)| 扇形熱圖+小提琴圖

前段時間在小紅書刷到了一個很有特色的熱力圖&#xff0c;由大佬滾筒洗衣機創作&#xff0c;感覺很有意思&#xff0c;嘗試 MATLAB 復刻&#xff1a; 作者使用的是 python 代碼&#xff0c;趕快去瞅瞅。 復刻效果 正文部分 0.數據準備 數據需要一個用來畫熱圖的矩陣以及一個…

批量PDF轉換工具,一鍵轉換Word Excel

軟件介紹 今天為大家推薦一款高效的Office文檔批量轉換工具&#xff0c;能夠快速將Word和Excel文件批量轉換為PDF格式。 軟件特點 這款名為"五五Excel word批量轉PDF"的工具體積小巧&#xff0c;不到2M大小&#xff0c;卻能實現強大的批量轉換功能&#xff0c…

面試150 基本計算器

思路 利用棧&#xff08;stack&#xff09;來保存進入括號前的計算狀態&#xff08;包括當前計算結果和符號&#xff09;&#xff0c;以便在括號結束后正確恢復計算上下文。代碼通過遍歷字符串&#xff0c;識別數字、加號、減號和括號。遇到數字時構造完整數值&#xff1b;遇到…

源哈希(sh)解析

源哈希&#xff08;Source Hashing&#xff09;是一種負載均衡算法&#xff0c;它根據請求的源 IP 地址&#xff08;或其他標識符&#xff09;生成哈希值&#xff0c;然后根據這個哈希值將請求分配到特定的后端服務實例。這種方法常用于確保來自同一客戶端的請求始終被路由到同…

axios的使用以及封裝

前言&#xff1a; 在現代前端開發中&#xff0c;網絡請求是不可避免的核心功能之一。無論是獲取后端數據、提交表單信息&#xff0c;還是與第三方 API 交互&#xff0c;高效且可靠的 HTTP 請求庫至關重要。axios 作為一款基于 Promise 的 HTTP 客戶端&#xff0c;憑借其簡潔的 …

github上部署自己的靜態項目

前置知識1、要在github部署項目要提交打包后的靜態文件(html,css&#xff0c;js)到倉庫里2、我們看下github所提供給我們的部署方式有啥&#xff0c;如下所見&#xff1b;要么是/root文件夾&#xff08;就說倉庫里全是打包后的產物&#xff1a;html,css&#xff0c;js要全部放到…

能源管理綜合平臺——分布式能源項目一站式監控

綜合性的能源企業管理面臨著項目多、分布散、信息孤島等問題&#xff0c;分布式的多項目能源在線監控管理平臺是一種集成了多個能源項目的數據采集、監控、分析和管理的系統。平臺集成GIS能力&#xff0c;能夠展示項目的整體分布態勢&#xff0c;對不同地點、不同類型的能源項目…

修改阿里云vps為自定義用戶登錄

win系統上找到控制面板-->用戶賬戶-->更改賬戶類型點擊更改賬戶類型&#xff0c;此時我們看到vps的默認管理員賬戶Administrator。為了防止vps被別人使用默認賬戶Administrator攻擊&#xff0c;我們添加一個用戶賬戶&#xff0c;點擊添加用戶賬戶。 用戶名建議奇葩點&…