欢迎光临
我们一直在努力

《堆 / 栈 / 静态区区别、内存泄漏原因及排查》

一、C++ 内存区域整体划分(经典五区)

┌───────────────┐
│ 栈 (Stack) │ ← 函数调用、局部变量
├───────────────┤
│ 堆 (Heap) │ ← new / malloc
├───────────────┤
│ 全局/静态区 │ ← global / static
├───────────────┤
│ 常量区 │ ← 字符串字面量、const
├───────────────┤
│ 代码区 │ ← 程序指令
└───────────────┘

1. 高频坑点

  • 仅堆内存会发生「内存泄漏」:栈和静态区由编译器 / 操作系统自动管理,不存在泄漏问题;
  • 栈溢出 ≠ 内存泄漏:栈溢出是栈空间被耗尽(如递归过深),泄漏是堆内存未手动释放;
  • 内存泄漏的核心是「可访问性丢失」:堆内存被分配后,失去了所有指向它的指针,既无法使用也无法释放,并非单纯的 “忘记释放”。
  • 2. 栈(stack)

  • 由编译器进行管理,在需要时由编译器自动分配空间,在不需要时候自动回收空间,一般保存的是局部变量和函数参数等。
  • 大多数编译器中,参数是从右向左入栈,函数调用完成后,局部变量先出栈,然后是参数,最后是栈顶指针最开始存放的地址,程序 由该点继续运行,不会产生碎片。
  • 栈是高地址向低地址扩展,空间较小
  • 高地址
    |
    | [栈底] ← 初始位置(固定)
    |
    | [数据]
    |
    | [栈顶] ← 动态移动(随压栈/出栈变化)
    |
    低地址

    3. 堆(heap)

  • 由程员手动管理(newmallocdeletefree)进行分配和回收,如果不进行回收的话,会造成内存泄漏的问题。
  • 不连续的空间,实际上系统中有一个空闲链表,当有程序申请的时候,系统遍历空闲链表找到第一个大于等于申请大小的空间分配给程序,如果有剩余的,也会将剩余的插入到空闲链表中,这也是产生内存碎片的原因。
  • 堆是低地址向高地址扩展,空间较大,较为灵活。
  • 高地址
    |
    | [栈底] ← 栈从高地址开始,向低地址扩展
    |
    | [栈顶]
    |
    | (空闲内存区域)
    |
    | [堆末尾] ← 堆从低地址开始,向高地址扩展
    |
    | [堆起始地址]
    低地址

    4. 静态区(static/global 区)

    特点: 1.程序启动时分配 2.程序结束时释放 3.整个程序生命周期

    存什么:

    int g = 10; // 全局变量
    static int s = 20; // 静态变量

    void foo() {
    static int x = 0; // 局部静态变量
    }

    对比点栈堆静态区
    分配方式 自动 手动 自动
    释放方式 自动 手动 程序结束
    生命周期 函数级 程序员控制 程序级
    大小
    速度
    泄漏风险

    二、内存泄漏的本质与核心原因(面试必讲)

    1. 内存泄漏的标准定义

    堆内存泄漏:程序员通过new/malloc在堆上分配了内存,后续既没有执行delete/free释放,又失去了所有指向该堆内存的指针,导致这块内存成为「不可访问的垃圾内存」—— 程序运行期间无法再使用,也无法释放,直到程序退出后由操作系统回收。

    //new 了不 delete

    void foo() {
    int* p = new int(10);
    } // 忘记 delete

    //指针被覆盖
    int* p = new int(10);
    p = new int(20); // 原内存丢失

    //️ new[] / delete 不匹配
    int* arr = new int[10];
    delete arr; // 应该 delete[]

    //循环容器中持续 new
    for (;;) {
    new int(1);
    }

    //️ 异常提前 return
    int* p = new int;
    if (error) return; // 泄漏

    // 智能指针循环引用
    shared_ptr<A> a;
    shared_ptr<B> b;
    a->b = b;
    b->a = a; // 永不释放

    2. 核心根源:堆内存的「手动管理特性」

    栈和静态区无泄漏的原因是无需程序员干预,而堆内存是程序员全权负责的: 编译器 / 操作系统不会主动回收堆内存(即使指针失效); 只要程序员未执行释放操作,且指向堆内存的指针被销毁 / 置空 / 重赋值,就会导致泄漏。

    三、内存泄漏的排查与根治方法

    1.代码规范自查

  • 遵循「谁分配,谁释放」原则:分配堆内存的函数,要么自己释放,要么明确告知调用者需要释放(如返回堆指针时加注释);
  • new/delete、malloc/free严格匹配:new→delete,new[]→delete[],malloc→free,禁止交叉使用;
  • 指针释放后立即置空:避免野指针,也能防止重复释放(delete nullptr是安全的);
  • 分支 / 循环中确保释放:if-else/for中所有分支都要写释放代码,避免遗漏;
  • 异常场景下的安全释放:在catch块中释放堆内存,或使用RAII 机制;
  • 容器存储堆指针时,手动遍历释放;
  • 2.源头根治泄漏

    1.核心思想:RAII 原则(资源获取即初始化) 定义:将堆资源的分配(new) 绑定到对象的构造函数,将资源的释放(delete) 绑定到对象的析构函数—— 对象创建时分配资源,对象销毁时(栈帧销毁 / 作用域结束)自动调用析构释放资源,无需程序员手动管理。 本质:用栈的自动管理特性,管理堆的手动管理资源,从根源避免泄漏。

    核心实现:C++11 智能指针(RAII 的具体实现) 智能指针是封装了堆内存指针的模板类,本质是栈上的对象,析构时自动释放指向的堆内存,支持*和->操作,用法和普通指针一致,完全替代手动new/delete。

    赞(0)
    未经允许不得转载:171主机测评 » 《堆 / 栈 / 静态区区别、内存泄漏原因及排查》
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址