乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

内存管理常见陷阱与排查

定义与作用

C++ 手动内存管理是多数严重 bug 的源头。本章覆盖五大陷阱:悬挂指针、双重释放、内存泄漏、缓冲区溢出和循环引用,并介绍 ASan 排查方法。

核心原理

五大陷阱关系图

完整示例

示例一:悬挂指针与智能指针修复

场景说明:小崔调试飞翔科技用户管理系统中的悬挂指针 bug。

#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct User {
    int id;
    std::string name;

    User(int i, std::string n) : id(i), name(std::move(n)) {}
};

// ❌ 危险代码:返回裸指针
User* createUserBad(int id, const std::string& name) {
    User u(id, name);
    return &u;  // ❌ 返回局部变量的地址!悬空指针
}

// ✅ 安全代码:返回智能指针
std::unique_ptr<User> createUserSafe(int id, const std::string& name) {
    return std::make_unique<User>(id, name);
}

// ❌ 危险代码:裸指针容器
void rawPointerTrap() {
    std::vector<User*> users;
    users.push_back(new User(1, "大翔"));
    users.push_back(new User(2, "白歌"));

    // 若此处抛异常,下方 delete 不会执行 → 泄漏
    for (auto* u : users)
        delete u;  // 手动释放,容易遗忘
}

// ✅ 安全代码:智能指针容器
void smartPointerSafe() {
    std::vector<std::unique_ptr<User>> users;
    users.push_back(std::make_unique<User>(1, "大翔"));
    users.push_back(std::make_unique<User>(2, "白歌"));
    // 自动释放,即使异常也安全
}

int main() {
    std::cout << "=== 飞翔科技内存安全审查 ===\n\n";

    // 1. 悬挂指针检测
    std::cout << "--- 悬挂指针 ---\n";
    auto u = createUserSafe(100, "孔蓝");
    std::cout << "用户: " << u->name << " (ID:" << u->id << ")\n\n";

    // 2. 双重释放陷阱
    std::cout << "--- 双重释放 ---\n";
    {
        auto p = std::make_unique<int>(42);
        // p 离开作用域自动释放,不可能双重释放
        std::cout << "unique_ptr 自动管理,不会双重释放\n";
    }

    // 3. 内存泄漏对比
    std::cout << "\n--- 内存泄漏 ---\n";
    // ❌ 裸指针容易遗忘 delete
    // int* leaked = new int[1000];
    // 如果提前返回或抛异常,这 1000 个 int 泄漏

    // ✅ RAII 保证释放
    auto safe = std::make_unique<int[]>(1000);
    std::cout << "make_unique 保证即使在异常路径也释放\n";

    // 4. 循环引用陷阱
    std::cout << "\n--- 循环引用 ---\n";
    struct Group;
    struct Member {
        std::string name;
        std::shared_ptr<Group> group;   // 可能引起循环引用
        ~Member() { std::cout << "  释放成员 " << name << "\n"; }
    };
    struct Group {
        std::string name;
        std::vector<std::shared_ptr<Member>> members;
        ~Group() { std::cout << "  释放群组 " << name << "\n"; }
    };

    {
        auto group = std::make_shared<Group>();
        group->name = "飞翔技术群";

        auto member = std::make_shared<Member>();
        member->name = "大翔";
        member->group = group;      // member 持有 group
        group->members.push_back(member);  // group 持有 member
        // 离开作用域:group 和 member 互相持有,永不释放!
    }
    std::cout << "  (注意:上面两个对象未被释放——循环引用)\n";

    // 5. 解决方案:weak_ptr 打破循环
    std::cout << "\n--- 修复:weak_ptr ---\n";
    struct FixedMember {
        std::string name;
        std::weak_ptr<Group> group;  // 弱引用不增加计数
        ~FixedMember() { std::cout << "  释放成员 " << name << "\n"; }
    };
    // 其他相同... 使用 weak_ptr 即可打破循环
}

预期输出:

=== 飞翔科技内存安全审查 ===

--- 悬挂指针 ---
用户: 孔蓝 (ID:100)

--- 双重释放 ---
unique_ptr 自动管理,不会双重释放

--- 内存泄漏 ---
make_unique 保证即使在异常路径也释放

--- 循环引用 ---
  (注意:上面两个对象未被释放——循环引用)

--- 修复:weak_ptr ---

逐段分析:

  • 悬挂指针最常见来源:返回局部变量地址、delete 后未置空
  • 智能指针从根本上消除手动 delete
  • 循环引用是 shared_ptr 的经典陷阱,weak_ptr 是标准解方

示例二:ASan 内存错误检测

场景说明:用 AddressSanitizer 检测堆缓冲区溢出和使用后释放。

// 编译: g++ -fsanitize=address -g -O1 main.cpp
// 运行时会自动检测内存错误

#include <iostream>
#include <cstring>

void heapBufferOverflow() {
    int* arr = new int[3]{1, 2, 3};
    arr[3] = 42;    // ❌ 越界写入!ASan 会报告
    delete[] arr;
}

void useAfterFree() {
    int* p = new int(42);
    delete p;
    std::cout << *p;  // ❌ heap-use-after-free!ASan 会报告
}

void stackBufferOverflow() {
    int arr[3] = {1, 2, 3};
    arr[3] = 42;  // ❌ 栈缓冲区溢出!ASan 会报告
}

int main() {
    // 取消注释以下调用以触发 ASan 报告
    // heapBufferOverflow();
    // useAfterFree();
    // stackBufferOverflow();

    std::cout << "=== ASan 内存错误检测 ===\n\n";
    std::cout << "编译: g++ -fsanitize=address -g main.cpp\n";
    std::cout << "运行时会自动检测:\n";
    std::cout << "  - heap-buffer-overflow\n";
    std::cout << "  - heap-use-after-free\n";
    std::cout << "  - stack-buffer-overflow\n";
    std::cout << "  - memory leak\n";
    std::cout << "\nASan 报告包含: 错误类型、文件名、行号、内存分配位置\n";
}

排查工具速查

工具平台检测内容性能开销
AddressSanitizer (ASan)GCC/Clang/MSVC缓冲区溢出、使用后释放、双重释放~2x
LeakSanitizer (LSan)GCC/Clang内存泄漏(ASan 内置)微小
Valgrind MemcheckLinux/macOS未初始化内存、泄漏、越界~20x
Application VerifierWindows堆破坏、句柄泄漏~5x
Dr. MemoryWindows/Linux未初始化读取、泄漏~10x

防御性编程检查单

规则说明
用智能指针替代裸指针unique_ptr 默认选择
delete 后立即置 nullptr可检测 use-after-free(有限)
优先用容器而非 C 数组vector/array 带边界检查
避免循环引用shared_ptr 环中用 weak_ptr
启用 ASan 测试CI 中开启 -fsanitize=address
遵循 Rule of Five自定义资源管理类需正确实现
用 RAII 管理所有资源文件/锁/套接字/内存

易错场景与面试考点

易错场景

1. 用 new 但不用 delete

void leak() {
    auto* p = new int[100];
    if (condition) return;  // ❌ 提前返回,泄漏!
    delete[] p;
}
// 解决:auto p = std::make_unique<int[]>(100);

2. 在构造函数中抛异常

class Bad {
    int* p1;
    int* p2;
public:
    Bad() : p1(new int), p2(new int) {
        throw std::runtime_error("oops");  // ❌ p1 泄漏!
    }
};
// 解决:使用智能指针或 try-catch

面试考点

考点要点
悬挂指针delete 后不置空,返回局部变量引用
双重释放同一地址 delete 两次,多个指针指向同一对象
内存泄漏new 后无对应 delete,异常路径忘记释放
循环引用shared_ptr 环,weak_ptr 打破
RAII资源生命周期 = 对象生命周期,异常安全
ASan编译期插桩,运行时检测,可与 CI 集成
三/五法则自定义资源管理类型需正确定义特殊成员函数
上一页
多线程面试题与实战