栈内存由编译器自动管理,生命周期绑定作用域;堆内存需手动或RAII管理,灵活但易出错;优先栈分配小对象,堆用于动态大小、共享或大对象。

栈内存由编译器自动分配和释放,生命周期与作用域严格绑定
函数调用时,局部变量(如 int x = 42;、std::string s = "hello";)直接在栈上分配;函数返回时,整个栈帧被弹出,这些变量**立刻失效**,无需手动干预。这种机制快、确定、无碎片,但容量有限(通常几 MB),且无法在函数外继续使用。
常见错误是返回局部变量的地址:
int* bad() {
int val = 100;
return &val; // 错!val 出作用域后栈内存已被回收
}调用后解引用该指针会触发未定义行为(UB),可能崩溃或输出随机值。
堆内存需手动申请和释放,生命周期由程序员控制
使用 new 和 delete(或 new[]/delete[])在堆上动态分配内存,比如 int* p = new int(42);。这块内存不会随函数结束而消失,可跨函数传递、长期持有,但也带来风险:
- 忘记
delete→ 内存泄漏(valgrind可检测) - 重复
delete同一指针 → 崩溃或静默损坏 - 用
delete释放new[]分配的数组 → UB - 释放后继续访问(悬垂指针)→ 行为不可预测
现代 C++ 更推荐用 RAII 容器替代裸指针管理堆内存
手动 new/delete 容易出错,标准库提供更安全的替代方案:
-
std::vector:自动管理连续堆内存,扩容/析构时内部完成new/delete -
std::unique_ptr:独占所有权,离开作用域自动delete,不能拷贝(避免误释放) -
std::shared_ptr:引用计数,最后一个副本析构时才释放内存
例如:
void safe_example() {
std::vector v = {1, 2, 3}; // 栈上对象,堆上数据,自动清理
auto ptr = std::make_unique(42); // 堆分配,离开作用域自动 delete
auto sptr = std::make_shared("heap-managed");
} 这些容器和智能指针把“手动管理”封装进构造/析构逻辑中,既保留堆的灵活性,又规避了裸指针的大部分陷阱。
栈 vs 堆的性能与适用场景差异很实际
栈分配几乎只是移动栈指针(纳秒级),堆分配涉及查找空闲块、可能触发系统调用(微秒到毫秒级),且频繁小块分配易导致碎片。所以:
本文档主要讲述的是关于Objective-C手动内存管理的规则;在ios开发中Objective-C 增加了一些新的东西,包括属性和垃圾回收。那么,我们在学习Objective-C之前,最好应该先了解,从前是什么样的,为什么Objective-C 要增加这些支持。有需要的朋友可以下载看看
立即学习“C++免费学习笔记(深入)”;
- 优先用栈:小对象、生命周期明确、不跨作用域传递
- 必须用堆:对象大小运行时才知道(如用户输入决定数组长度)、需共享或延长生命周期、对象过大(如百万元素数组)可能撑爆栈
- 避免混合:比如在栈上放
std::string(它内部堆分配字符数据),但字符串对象本身仍在栈上——这是标准库的设计权衡,不用抵触
真正容易被忽略的是:栈溢出往往没有明确报错,而是静默破坏相邻变量或导致段错误,尤其在递归过深或大数组声明(char buf[1024*1024];)时。这类问题调试成本远高于早用 std::vector 或堆分配。









