news 2026/7/22 9:32:23

C++内存管理全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++内存管理全攻略

好的,我们来详细讲解 C++ 中的内存管理。理解 C++ 的内存管理机制对于编写高效、安全、无内存泄漏的程序至关重要。

1. 内存区域概述

C++ 程序在运行时使用的内存通常被划分为几个关键区域:

  • 栈 (Stack):用于存储局部变量、函数参数和返回地址。栈内存的管理是自动的、高效的。当函数被调用时,其局部变量在栈上分配;当函数返回时,这些变量所占用的内存会被自动释放。栈的分配和释放遵循后进先出 (LIFO) 原则。
  • 堆 (Heap):也称为“自由存储区”。这块内存区域由程序员显式地管理。程序员通过特定的操作(如new)在堆上申请内存,并在不再需要时通过对应的操作(如delete)释放内存。如果管理不当(如忘记释放),会导致内存泄漏
  • 全局/静态存储区:用于存储全局变量和静态变量(包括类的静态成员变量)。这些变量在程序开始运行时分配内存,在程序结束时释放。
  • 常量存储区:用于存储字符串常量和其他常量。
  • 代码区:用于存储程序的执行代码(编译后的机器指令)。

2. 动态内存分配 (newdelete)

这是 C++ 管理堆内存的核心机制。

  • new运算符:用于在堆上分配内存。
    • 分配单个对象:int *ptr = new int;// 分配一个 int 大小的内存,并返回指向它的指针
    • 分配对象数组:int *arr = new int[10];// 分配一个包含 10 个 int 的数组,返回指向第一个元素的指针
    • 初始化:int *ptr = new int(42);// 分配内存并初始化为 42
    • new在分配失败时会抛出std::bad_alloc异常(除非使用nothrow版本)。
  • delete运算符:用于释放由new分配的内存。
    • 释放单个对象:delete ptr;//ptr必须是指向由new分配的单个对象的指针
    • 释放对象数组:delete[] arr;//arr必须是指向由new[]分配的数组的指针
    • 注意:必须配对使用newdelete,以及new[]delete[]。错误配对(如用delete释放数组或用delete[]释放单个对象)会导致未定义行为,通常是程序崩溃。
  • mallocfree(C 风格):C++ 兼容 C 的内存管理函数,但不推荐在 C++ 中优先使用,因为:
    • malloc只分配内存,不调用构造函数。
    • free只释放内存,不调用析构函数。
    • 使用它们管理 C++ 对象(尤其是带有资源的类)极易出错。

3. 内存泄漏 (Memory Leak)

内存泄漏是指程序在堆上分配了内存,但之后失去了对这块内存的引用(指针丢失),且未能释放它。后果是:

  • 程序占用的内存会不断增长,最终可能导致耗尽可用内存。
  • 系统性能下降。
  • 对于长时间运行的程序(如服务器、守护进程)尤为致命。

常见泄漏场景:

  • 忘记调用deletedelete[]
  • 指针被重新赋值前未释放旧内存。
    int* ptr = new int; ptr = new int; // 第一个 new 分配的内存泄漏了!
  • 异常导致delete未执行。
    try { int* ptr = new int; // ... 可能抛出异常的代码 ... delete ptr; } catch (...) { // 如果异常发生在 delete 之前,ptr 指向的内存泄漏 }

4. 智能指针 (Smart Pointers - C++11 及以后)

为了解决手动管理内存(new/delete)的复杂性和易出错性(尤其是内存泄漏和异常安全问题),C++11 引入了智能指针。它们在<memory>头文件中定义。

  • std::unique_ptr:表示对动态分配对象的独占所有权
    • 同一时间只有一个unique_ptr可以指向该对象。
    • unique_ptr被销毁(如离开作用域)时,它所指向的对象会被自动删除。
    • 不支持拷贝(会报错),但支持移动(转移所有权)。
    • 示例:
      #include <memory> { std::unique_ptr<int> uptr(new int(10)); // 创建并管理一个 int // 使用 *uptr 访问 } // uptr 离开作用域,自动调用 delete 释放内存
  • std::shared_ptr:表示对动态分配对象的共享所有权
    • 多个shared_ptr可以指向同一个对象。
    • 内部使用引用计数跟踪指向对象的指针数量。
    • 当最后一个shared_ptr被销毁或重置时,对象才会被删除。
    • 支持拷贝和移动。
    • 示例:
      #include <memory> { std::shared_ptr<int> sptr1 = std::make_shared<int>(20); // 更好,效率更高且更安全 { std::shared_ptr<int> sptr2 = sptr1; // 拷贝,引用计数+1 // 两者都指向同一个 int } // sptr2 销毁,引用计数-1 } // sptr1 销毁,引用计数变为0,自动删除对象
  • std::weak_ptr:shared_ptr的辅助指针,不增加引用计数
    • 用于解决shared_ptr的循环引用问题(两个或多个shared_ptr互相引用导致对象无法释放)。
    • 不能直接访问对象,需要先通过lock()方法尝试提升为shared_ptr(如果对象还存在)。
    • 示例:
      #include <memory> std::shared_ptr<int> sptr = std::make_shared<int>(30); std::weak_ptr<int> wptr = sptr; // 创建 weak_ptr 指向同一个对象 { std::shared_ptr<int> sptr2 = wptr.lock(); // 尝试提升,如果对象还在,sptr2 有效 if (sptr2) { // 安全地使用 sptr2 } } // sptr2 销毁 ``` // sptr 销毁后,wptr.lock() 会返回空 shared_ptr

std::make_unique(C++14) 和std::make_shared(C++11):

  • 推荐使用这些函数模板来创建智能指针管理的对象。
  • 优点:
    • 更安全:避免了直接使用new(如果后续代码抛出异常,new的结果没有被智能指针捕获,可能泄漏)。
    • 更高效(尤其对于make_shared):可能将对象和控制块(存储引用计数等信息)分配在连续的内存区域。

5. 总结与最佳实践

  • 优先使用栈内存:对于生命周期局限于函数或作用域的变量,优先在栈上分配。安全、高效、自动管理。
  • 避免裸指针管理所有权:尽量避免手动使用newdelete来管理对象的生命周期。这是现代 C++ 的核心建议。
  • 善用智能指针:
    • 需要单一所有权时,使用unique_ptr。简单、高效。
    • 需要共享所有权时,使用shared_ptrweak_ptr。注意避免循环引用。
    • 使用make_uniquemake_shared来创建它们管理的对象。
  • 理解所有权:清晰界定哪个部分(函数、类、智能指针)负责释放动态分配的资源。
  • 资源获取即初始化 (RAII):将资源(如内存、文件句柄、网络连接)的获取封装在类的构造函数中,并在析构函数中释放资源。智能指针就是 RAII 思想应用于内存管理的典范。

示例:综合运用

#include <iostream> #include <memory> #include <vector> class MyClass { public: MyClass(int v) : value(v) { std::cout << "Constructing " << value << std::endl; } ~MyClass() { std::cout << "Destructing " << value << std::endl; } void print() { std::cout << "Value: " << value << std::endl; } private: int value; }; int main() { // 栈上对象 - 自动管理 MyClass stackObj(1); // unique_ptr 管理单个对象 std::unique_ptr<MyClass> uptr = std::make_unique<MyClass>(2); // shared_ptr 管理共享对象 std::shared_ptr<MyClass> sptr1 = std::make_shared<MyClass>(3); { std::shared_ptr<MyClass> sptr2 = sptr1; // 共享 sptr2->print(); // 使用箭头运算符访问成员 } // sptr2 析构,对象还在(sptr1 还在) // 容器存储智能指针 std::vector<std::unique_ptr<MyClass>> vec; vec.push_back(std::make_unique<MyClass>(4)); vec.push_back(std::make_unique<MyClass>(5)); // 访问容器中的元素 vec[0]->print(); return 0; // 离开 main 作用域: // stackObj 析构 // uptr 析构 -> 其管理的对象 (2) 析构 // sptr1 析构 -> 引用计数归零 -> 对象 (3) 析构 // vec 析构 -> 其包含的两个 unique_ptr 析构 -> 对象 (4) 和 (5) 析构 } /* 输出示例: Constructing 1 Constructing 2 Constructing 3 Value: 3 Constructing 4 Constructing 5 Value: 4 Destructing 1 Destructing 5 Destructing 4 Destructing 3 Destructing 2 */

这个示例展示了栈对象、unique_ptrshared_ptr以及如何在容器中使用智能指针。注意析构的顺序(栈对象按创建相反顺序析构,智能指针管理的对象在其指针析构时析构)。通过使用智能指针,我们完全避免了显式的newdelete,大大降低了内存泄漏的风险。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/21 18:10:43

Python中的PO模型的实例

1.PO模型在自动化中经常会被使用到&#xff0c;一下就是PO模型的具体实例2.实现BasePagefrom selenium import webdriver from selenium.webdriver.common.action_chains import ActionChains #鼠标操作class BasePage():BasePage封装所有界面都公用的方法。例如driver,find_el…

作者头像 李华
网站建设 2026/7/17 23:14:49

DVDFab

链接&#xff1a;https://pan.quark.cn/s/5407ee81f93dDVDFab13是一款专业的蓝光刻录转换器&#xff0c;软件提供了一套完备DVD/(4K UHD) 蓝光/视频/图像解决方案&#xff0c;其中就包含了DVD Copy、DVD Ripper、DVD Creator、DVD to Blu-ray Converter等服务功能。与上一代产品…

作者头像 李华
网站建设 2026/7/17 3:56:16

微信个人号api开发

在微信深度渗透社交与商业场景的今天&#xff0c;个人微信号已成为企业客户运营、用户触达的核心载体。WTAPI框架作为专注微信个人号二次开发的私有API解决方案&#xff0c;通过深度封装微信底层协议与RPA技术&#xff0c;将复杂的微信功能转化为可调用的标准化API接口&#xf…

作者头像 李华
网站建设 2026/7/17 3:40:49

开源版 Coze: 创建智能体-每日 ERP 系统巡检计划

使用开源版 Coze 创建智能体&#xff1a;每日 ERP 系统巡检计划 说明&#xff1a;本文基于 开源版 Coze Studio&#xff08;v1.2.0&#xff09; 的真实能力编写&#xff0c;所有操作均可在本地部署环境中复现。 目标&#xff1a;构建一个可自动执行或辅助人工完成 每日 ERP 系统…

作者头像 李华
网站建设 2026/7/16 13:18:26

playwright元素定位

法一&#xff1a;placeholder page.get_by_placeholder("请输入密码") 法二&#xff1a;css方式1 page.locator(input[placeholder"请输入密码"]) 法三&#xff1a; page.locator(span[class*"password-Style"] input) 法四&#xff1a;CS…

作者头像 李华