C++内存泄漏的成因和检测方法有哪些

来源:建站作者:零壳头衔:程序员
导读:本期聚焦于小伙伴创作的《C++内存泄漏的成因和检测方法有哪些》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++内存泄漏的成因和检测方法有哪些》有用,将其分享出去将是对创作者最好的鼓励。

C++作为一门支持手动内存管理的编程语言,开发者需要自行控制动态内存的申请和释放,这也使得内存泄漏成为C++项目中高发的问题。内存泄漏指的是程序动态申请的内存在使用完毕后没有被正确释放,导致这部分内存无法被后续程序使用,长期运行会不断消耗系统内存资源。

C++内存泄漏的成因和检测方法有哪些

C++内存泄漏的常见成因

1. 动态内存申请未释放

使用new或者new[]申请的内存,没有对应使用delete或者delete[]释放,是最基础的内存泄漏原因。比如下面的代码就会出现内存泄漏:

#include <iostream>

void test_leak() {
    // 使用new申请了int类型的内存,没有释放
    int* p = new int(10);
    // 函数结束,p指针被销毁,指向的内存无法再被访问,也没有释放
}

int main() {
    test_leak();
    return 0;
}

2. 释放逻辑被异常中断

如果动态内存申请之后,释放内存的代码之前出现了异常,导致程序跳出了当前作用域,没有执行到释放逻辑,也会产生内存泄漏。例如:

#include <iostream>
#include <stdexcept>

void test_exception_leak() {
    int* p = new int(20);
    // 这里抛出异常,后续delete不会执行
    throw std::runtime_error("发生异常");
    delete p;
}

int main() {
    try {
        test_exception_leak();
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
    }
    return 0;
}

3. 指针被重复赋值覆盖

当一个指针指向了新申请的内存,之前指向的旧内存地址没有被保存,也没有被释放,旧内存就会泄漏。比如:

#include <iostream>

void test_overwrite_leak() {
    int* p = new int(30);
    // p重新指向新的内存,原来的内存地址丢失,无法释放
    p = new int(40);
    delete p; // 只释放了新申请的内存,旧内存泄漏
}

int main() {
    test_overwrite_leak();
    return 0;
}

4. 基类析构函数未声明为虚函数

当使用基类指针指向派生类对象,并且通过delete基类指针释放内存时,如果基类析构函数不是虚函数,只会调用基类的析构函数,派生类的资源不会被释放,导致内存泄漏。示例代码如下:

#include <iostream>

class Base {
public:
    // 析构函数不是虚函数
    ~Base() {
        std::cout << "Base destructor" << std::endl;
    }
};

class Derived : public Base {
public:
    int* data;
    Derived() {
        data = new int(50);
    }
    ~Derived() {
        delete data;
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Base* p = new Derived();
    // 只调用Base的析构函数,Derived的data没有释放
    delete p;
    return 0;
}

C++内存泄漏的检测方法

1. 代码层面手动排查

对于小型项目,可以通过梳理所有newdelete的配对情况来排查。可以自定义内存管理函数,在申请和释放内存时记录日志,方便后续追踪内存的使用情况。比如下面的简单示例:

#include <iostream>
#include <map>

std::map<void*, std::string> mem_record;

void* my_new(size_t size, const std::string& tag) {
    void* p = malloc(size);
    mem_record[p] = tag;
    std::cout << "申请内存:" << p << ",标签:" << tag << std::endl;
    return p;
}

void my_delete(void* p) {
    auto it = mem_record.find(p);
    if (it != mem_record.end()) {
        std::cout << "释放内存:" << p << ",标签:" << it->second << std::endl;
        mem_record.erase(it);
    }
    free(p);
}

int main() {
    int* p1 = (int*)my_new(sizeof(int), "p1");
    int* p2 = (int*)my_new(sizeof(int), "p2");
    my_delete(p1);
    // p2没有释放,最后输出记录可以看到未释放的内存
    for (auto& item : mem_record) {
        std::cout << "未释放内存:" << item.first << ",标签:" << item.second << std::endl;
    }
    return 0;
}

2. 使用智能指针规避泄漏

C++11之后引入了智能指针,通过RAII机制自动管理内存,不需要手动调用delete,可以从根源上减少内存泄漏的发生。常用的智能指针有std::unique_ptrstd::shared_ptr

  • std::unique_ptr:独占所有权的智能指针,离开作用域会自动释放内存,不能拷贝只能移动。
  • std::shared_ptr:共享所有权的智能指针,通过引用计数管理内存,当引用计数为0时自动释放。

使用智能指针改写之前的第一个泄漏示例:

#include <iostream>
#include <memory>

void test_no_leak() {
    // 使用unique_ptr管理内存,函数结束自动释放
    std::unique_ptr<int> p = std::make_unique<int>(10);
}

int main() {
    test_no_leak();
    return 0;
}

3. 借助第三方检测工具

对于大型项目,手动排查效率很低,可以使用专业的第三方工具检测内存泄漏,常用的工具包括:

工具名称适用平台特点
ValgrindLinux开源工具,不仅可以检测内存泄漏,还能检测内存越界、使用未初始化内存等问题,使用简单,只需要直接运行程序即可
Visual Studio 诊断工具Windows集成在Visual Studio中,调试时可以直接查看内存泄漏的报告,定位泄漏的代码位置
AddressSanitizer跨平台编译器内置的检测工具,GCC和Clang都支持,编译时添加对应参数即可开启检测,性能开销较小

以Valgrind为例,检测内存泄漏的命令如下:

# 假设编译后的可执行文件名为test
valgrind --leak-check=full ./test

运行后会输出详细的内存泄漏信息,包括泄漏的内存大小、对应的代码位置,方便开发者快速定位问题。

总结

C++内存泄漏的成因大多和手动内存管理的疏漏有关,开发者在编写代码时需要严格保证newdelete的配对,同时尽量使用智能指针替代裸指针管理动态内存。对于已经出现的内存泄漏问题,可以根据项目规模选择合适的检测方法,小型项目手动排查,大型项目借助第三方工具快速定位,从而有效解决内存泄漏问题,提升程序的运行稳定性。

C++内存泄漏new_delete智能指针Valgrind修改时间:2026-07-10 18:42:37

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。