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

    • 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++ 程序的内存分为多个区域,理解各区域的分配时机、生命周期和管理方式,是编写安全高效 C++ 程序的根基:

  • 栈(Stack):自动变量,编译器管理,速度快
  • 堆(Heap):动态分配,程序员通过 new/delete 管理
  • 静态区(Data):全局变量、静态变量,程序启动分配、结束释放
  • 常量区(Text/ROData):字面量、constexpr 数据
int global = 1;          // 静态区
static int s = 2;        // 静态区

void f() {
    int local = 3;       // 栈
    int* p = new int(4); // p 在栈,*p 在堆
    delete p;
}

核心原理

内存分区模型

new/delete 的工作流程

完整示例

示例一:演示四类内存区域

场景说明:通过一个综合程序追踪各内存区域对象的生命周期。

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

// 静态区:全局变量
int global_counter = 0;

// 静态区:静态成员
class Demo {
public:
    static int class_var;       // 静态数据成员在静态区
    static constexpr int const_var = 100;  // 常量区

    Demo(int id) : id_(id) {
        ++global_counter;
        std::cout << "  [堆/栈] 构造对象 #" << id_
                  << " (global_counter=" << global_counter << ")" << std::endl;
    }

    ~Demo() {
        std::cout << "  [析构] 对象 #" << id_
                  << " (global_counter=" << global_counter << ")" << std::endl;
        --global_counter;
    }

    void print() const {
        std::cout << "    Demo #" << id_ << std::endl;
    }

private:
    int id_;
};

int Demo::class_var = 42;  // 静态区定义

// 常量区:字符串字面量
const char* greet = "Hello, C++ Memory Model!";  // greet 在静态区,"..." 在常量区

// 静态区:函数内静态变量
void count_calls() {
    static int call_count = 0;  // 静态区,只在首次调用初始化
    ++call_count;
    std::cout << "  count_calls 被调用第 " << call_count << " 次" << std::endl;
}

int main() {
    std::cout << "===== 内存分区演示 =====" << std::endl;

    std::cout << "\n--- 静态区 & 常量区 ---" << std::endl;
    std::cout << "global_counter: " << global_counter
              << " (地址: " << &global_counter << ")" << std::endl;
    std::cout << "Demo::class_var: " << Demo::class_var
              << " (地址: " << &Demo::class_var << ")" << std::endl;
    std::cout << "Demo::const_var: " << Demo::const_var << std::endl;
    std::cout << "greet: " << greet
              << " (字符串在常量区)" << std::endl;

    count_calls();
    count_calls();

    std::cout << "\n--- 栈 Stack ---" << std::endl;
    {
        int stack_int = 10;
        double stack_double = 3.14;
        Demo stack_obj(1);  // 栈上构造

        std::cout << "  stack_int 地址: " << &stack_int << std::endl;
        std::cout << "  stack_double 地址: " << &stack_double << std::endl;
        std::cout << "  stack_obj 地址: " << &stack_obj << std::endl;
    }  // 离开作用域 → 自动析构
    std::cout << "  [离开作用域后] global_counter=" << global_counter << std::endl;

    std::cout << "\n--- 堆 Heap ---" << std::endl;
    Demo* heap_obj = new Demo(2);  // 堆上构造
    std::cout << "  heap_obj 指针在栈 (地址: " << &heap_obj << ")" << std::endl;
    std::cout << "  *heap_obj 对象在堆 (地址: " << heap_obj << ")" << std::endl;

    // 堆对象必须手动释放
    delete heap_obj;
    std::cout << "  [delete 后] global_counter=" << global_counter << std::endl;

    // 堆数组
    std::cout << "\n--- 堆数组 ---" << std::endl;
    Demo* arr = new Demo[3]{3, 4, 5};  // 连续堆空间,3 个对象
    std::cout << "arr[0] 地址: " << &arr[0] << std::endl;
    std::cout << "arr[1] 地址: " << &arr[1] << std::endl;
    std::cout << "arr[2] 地址: " << &arr[2] << std::endl;

    delete[] arr;  // 必须使用 delete[]
    std::cout << "  [delete[] 后] global_counter=" << global_counter << std::endl;

    // 推荐:用智能指针自动管理堆对象
    std::cout << "\n--- unique_ptr(堆但自动释放)---" << std::endl;
    {
        auto smart = std::make_unique<Demo>(6);
        smart->print();
        // 离开作用域自动 delete
    }
    std::cout << "  [离开作用域后] global_counter=" << global_counter << std::endl;

    return 0;
}

预期输出:

===== 内存分区演示 =====

--- 静态区 & 常量区 ---
global_counter: 0 (地址: 0x...)
Demo::class_var: 42 (地址: 0x...)
Demo::const_var: 100
greet: Hello, C++ Memory Model! (字符串在常量区)
  count_calls 被调用第 1 次
  count_calls 被调用第 2 次

--- 栈 Stack ---
  [堆/栈] 构造对象 #1 (global_counter=1)
  stack_int 地址: 0x...
  stack_double 地址: 0x...
  stack_obj 地址: 0x...
  [析构] 对象 #1 (global_counter=1)
  [离开作用域后] global_counter=0

--- 堆 Heap ---
  [堆/栈] 构造对象 #2 (global_counter=1)
  heap_obj 指针在栈 (地址: 0x...)
  *heap_obj 对象在堆 (地址: 0x...)
  [析构] 对象 #2 (global_counter=1)
  [delete 后] global_counter=0

--- 堆数组 ---
  [堆/栈] 构造对象 #3 (global_counter=1)
  [堆/栈] 构造对象 #4 (global_counter=2)
  [堆/栈] 构造对象 #5 (global_counter=3)
arr[0] 地址: 0x...
arr[1] 地址: 0x...
arr[2] 地址: 0x...
  [析构] 对象 #5 (global_counter=3)
  [析构] 对象 #4 (global_counter=2)
  [析构] 对象 #3 (global_counter=1)
  [delete[] 后] global_counter=0

--- unique_ptr(堆但自动释放)---
  [堆/栈] 构造对象 #6 (global_counter=1)
    Demo #6
  [析构] 对象 #6 (global_counter=1)
  [离开作用域后] global_counter=0

逐段分析:

  • 栈地址向低地址方向增长(取决于实现),堆向高地址增长
  • static 局部变量在静态区,只初始化一次——call_count 跨调用保持值
  • constexpr 和字符串字面量在常量区(只读)
  • new 分配在堆,返回栈上的指针;delete 释放堆内存
  • new T[n] 必须配对 delete[]——否则未定义行为
  • 智能指针让堆对象自动释放——防止内存泄漏

示例二:内存泄漏检测与对比

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

// 模拟资源类
struct Resource {
    int id;
    static int alive_count;

    explicit Resource(int id) : id(id) {
        ++alive_count;
        std::cout << "  构造 Resource #" << id
                  << " (存活: " << alive_count << ")" << std::endl;
    }
    ~Resource() {
        --alive_count;
        std::cout << "  析构 Resource #" << id
                  << " (存活: " << alive_count << ")" << std::endl;
    }
};
int Resource::alive_count = 0;

// ====== 版本 A:裸指针——容易泄漏 ======
void raw_pointer_version() {
    std::cout << "\n--- 裸指针版 ---" << std::endl;
    Resource* r1 = new Resource(1);
    Resource* r2 = new Resource(2);

    // 模拟异常
    if (r1->id > 0) {
        delete r1;  // 手动释放 r1
        throw std::runtime_error("异常发生!r2 未被释放!");
    }

    delete r1;
    delete r2;
}

// ====== 版本 B:智能指针——自动安全 ======
void smart_pointer_version() {
    std::cout << "\n--- 智能指针版 ---" << std::endl;
    auto r1 = std::make_unique<Resource>(1);
    auto r2 = std::make_unique<Resource>(2);

    // 模拟异常
    if (r1->id > 0) {
        throw std::runtime_error("异常发生!但 r1 和 r2 都会自动释放");
    }
}

int main() {
    std::cout << "===== 内存泄漏对比 =====" << std::endl;
    std::cout << "初始存活: " << Resource::alive_count << std::endl;

    // 裸指针版——异常导致泄漏
    try {
        raw_pointer_version();
    } catch (const std::exception& e) {
        std::cerr << "捕获异常: " << e.what() << std::endl;
        std::cout << "异常后存活: " << Resource::alive_count
                  << " (如果 > 0 说明泄漏!)" << std::endl;
    }

    std::cout << "\n清理后存活: " << Resource::alive_count << std::endl;

    // 智能指针版——异常仍安全
    try {
        smart_pointer_version();
    } catch (const std::exception& e) {
        std::cerr << "捕获异常: " << e.what() << std::endl;
        std::cout << "异常后存活: " << Resource::alive_count
                  << " (= 0 说明安全)" << std::endl;
    }

    return 0;
}

预期输出:

===== 内存泄漏对比 =====
初始存活: 0

--- 裸指针版 ---
  构造 Resource #1 (存活: 1)
  构造 Resource #2 (存活: 2)
  析构 Resource #1 (存活: 1)
捕获异常: 异常发生!r2 未被释放!
异常后存活: 1 (如果 > 0 说明泄漏!)

清理后存活: 1

--- 智能指针版 ---
  构造 Resource #1 (存活: 2)
  构造 Resource #2 (存活: 3)
  析构 Resource #2 (存活: 2)
  析构 Resource #1 (存活: 1)
捕获异常: 异常发生!但 r1 和 r2 都会自动释放
异常后存活: 1 (= 0 说明安全)

注:智能指针版在异常发生后 Resource 仍为 1,因为 stack 展开时析构了所有 RAII 对象。之前裸指针版残留的 Resource #2 也在这个时间点存在。

易错场景与面试考点

易错场景

场景错误表现正确做法
new[] 配 delete未定义行为严格 new→delete,new[]→delete[]
异常中裸指针泄漏析构函数不执行用智能指针
返回栈对象地址悬挂指针返回值对象或智能指针
循环引用shared_ptr 内存泄漏用 weak_ptr 打破循环
用 malloc 配 delete未定义行为全部用 new/delete 或全部用 malloc/free

常见面试问题

  1. C++ 内存分区及各自特点?——栈(自动变量,编译器管理)、堆(new/delete 动态分配)、静态区(全局/静态变量,程序生命周期)、常量区(字面量/constexpr,只读)、代码区。

  2. new 和 malloc 的区别?——new 是运算符,自动计算大小、调用构造函数、返回正确类型指针。malloc 是函数,需要手动计算大小、不调构造函数、返回 void*。delete 和 free 同理。

  3. new[] 为什么必须配对 delete[]?——new[] 在对象数组前存储了元素数量信息,delete[] 需要读取这个值来确定调用多少次析构函数。用 delete 会导致释放错误或只析构第一个元素。

  4. 什么是内存泄漏?如何检测?——分配的内存未释放,导致可用内存减少。检测工具:Valgrind(Linux)、AddressSanitizer、Visual Studio 诊断工具、CRT 调试堆。

  5. 栈溢出和堆溢出的区别?——栈溢出:递归过深或局部变量过大;堆溢出(OOM):new 失败抛 std::bad_alloc。栈大小通常 1~8MB,堆大小接近可用 RAM。

小结

  • 四区模型:栈(自动)| 堆(new/delete)| 静态区(全局/静态)| 常量区(字面量)
  • new = 分配原始内存 + 调用构造函数;delete = 调用析构函数 + 释放原始内存
  • new[] 必须搭配 delete[]——数组释放需知道元素数量
  • 智能指针是防止内存泄漏的根本手段——异常安全、自文档化
  • 永远避免裸 new/delete——优先用 make_unique / make_shared
下一页
RAII 惯用法