栈变量作用域结束即销毁,堆内存需手动或智能指针管理;new/delete 与 malloc/free 不可混用;移动语义实现资源高效转移。

栈内存自动释放,但函数返回后变量就没了
栈上分配的变量生命周期和作用域完全绑定:进入作用域时分配,离开时立刻销毁。比如 int x = 42; 或 std::string s = "hello";,只要执行流走出当前作用域(比如函数结束、循环块结束),这些对象的析构函数就会被调用,内存也随即归还给栈。
常见错误现象:return &x; 返回局部变量地址,拿到的是悬垂指针;std::vector<int> v; { std::array<int> tmp = {1,2,3}; v.assign(tmp.begin(), tmp.end()); } </int></int> —— 这里没问题,因为 tmp 的内容被拷贝了,但若误写成 v.data() == tmp.data() 就会出错,因为 tmp 已销毁。
- 栈分配快、无手动管理成本,但大小受限(一般几 MB),超限会触发
stack overflow - 不要试图用
new在栈上分配(语法不支持),也不要对栈变量调用delete - 递归过深、大数组(如
char buf[1024*1024];)容易踩栈溢出坑
堆内存要自己 new/delete,或交给智能指针
堆内存由程序员显式申请(new)、显式释放(delete),生命周期不依赖作用域,而是靠你控制。但裸指针管理极易出错:忘了 delete → 内存泄漏;重复 delete → 未定义行为;delete 后继续用 → 悬垂指针。
现代 C++ 的解法是用智能指针接管所有权:std::unique_ptr 独占,std::shared_ptr 共享引用计数。它们在析构时自动调用 delete,前提是构造时确实指向堆内存(比如 new Widget)。
立即学习“C++免费学习笔记(深入)”;
-
std::unique_ptr<int> p1(new int(42));</int>正确;std::unique_ptr<int> p2(&x);</int>错误——栈变量不能交由智能指针释放 -
std::shared_ptr有引用计数开销,循环引用时需用std::weak_ptr打破 - 容器如
std::vector内部数据默认就在堆上,你只管它本身(栈上)的生命周期,不用操心底层内存
别混用 new/delete 和 malloc/free
C++ 的 new 不仅分配内存,还会调用构造函数;delete 会先调用析构函数再释放内存。而 malloc/free 是纯 C 风格,不涉及对象生命周期管理。混用会导致构造/析构跳过、内存损坏、甚至程序崩溃。
典型错误:int* p = (int*)malloc(sizeof(int)); delete p; —— 析构没意义(int 无析构),但行为未定义;更危险的是 Widget* w = new Widget(); free(w); —— 析构函数根本没运行,资源(如文件句柄、动态申请的子内存)全泄露。
- 一律用
new/delete(或智能指针)配对,除非对接纯 C 库且明确要求malloc -
new[]和delete[]必须严格配对,否则可能只析构第一个对象 - 自定义分配器(如
std::allocator)底层也可能调用malloc,但那是封装好的,使用者无需也不该直接混用
移动语义让堆资源转移变得安全又高效
当对象内部持有堆内存(比如 std::vector、std::string),拷贝代价高。C++11 引入移动语义,允许把资源“转手”而不是复制:源对象放弃资源,目标对象接手。这背后依赖的是右值引用和移动构造函数/移动赋值运算符。
例如 std::vector<int> a(1000000); std::vector<int> b = std::move(a);</int></int> 后,b 拿到原堆内存指针,a 变为空(但合法可析构)。如果不写 std::move,编译器可能仍做移动(RVO/NRVO 优化),但显式写出更清晰、更可控。
- 自己写类时,若含裸指针或堆资源,必须实现移动操作,否则默认拷贝会出问题
- 移动后原对象处于“有效但未指定状态”,只能赋值、析构或调用无副作用的 const 成员函数(如
empty()) - 移动不等于“更快”,它只是避免了深拷贝;如果类没管理堆资源,移动和拷贝性能一样
栈和堆的区别不在物理位置,而在管理契约:栈按作用域自动进出,堆按所有权手动或委托释放。最容易被忽略的是——很多你以为在用栈的地方,其实底层偷偷用了堆(比如 std::string 小字符串优化除外,大字符串一定在堆),而你以为能随便传指针的地方,可能早已经失效。









