写程序时,变量往哪儿放,其实挺有讲究。比如定义一个整数 int a = 10;,它大概率是放在“栈”里的。而用 new 或 malloc 动态申请的内存,比如创建一个对象,就跑到了“堆”上。那问题来了——栈和堆,到底哪个更快?
栈的速度:快得像翻书
栈的结构很简单,就像一摞书,只能从最上面拿或放。每次函数调用,系统就在栈上分配一块连续空间,函数结束就直接收回。这个过程只需要移动一下“栈顶指针”,几乎不花时间。
举个例子,你进电梯,每上一层楼就压入一个函数帧,下来就弹出。整个过程顺序清晰,CPU 缓存也喜欢这种连续访问方式,命中率高,自然快。
void func() {
int x = 5; // 栈上分配,瞬间完成
double y = 3.14; // 同样在栈上
} // 函数结束,x 和 y 自动消失
堆的速度:灵活但慢半拍
堆像是一个大仓库,谁都能申请空间,但管理起来复杂。每次 new 对象,系统得找一块够大的空地,还得记录这块地被谁用了,有没有碎片。这个过程叫“内存分配”,比栈的指针移动慢得多。
更麻烦的是,堆上的内存地址不连续。你今天申请的两块内存,可能一个在东城,一个在西郊。CPU 缓存预取机制在这种场景下容易扑空,性能就打折扣。
int* p = new int[1000]; // 堆上分配,需要查找空闲块
// 使用完还得手动 delete[] p;
实际场景中的差异
写个循环创建一万个对象,如果全扔堆上,程序明显变慢,还可能触发垃圾回收(比如 Java、C#)。而局部变量放在栈上,函数一退出就清掉,干净利落。
但这不代表堆没用。栈的空间有限,一般就几MB,函数调用太深或者放太大数组,就会“栈溢出”。这时候只能用堆,哪怕慢点也得扛着。
比如你要处理一张 4K 图片的像素数据,数组长度几十万,栈肯定装不下,只能申请堆内存。这就是典型的“要空间不要速度”的权衡。
语言层面的影响
在 C++ 里,你可以明确控制变量在栈还是堆。但像 Java,所有对象默认都在堆上,栈只存引用。这意味着即使你想快,也没法让对象本身在栈上分配——除非 JVM 做了逃逸分析优化。
Go 语言有点聪明,编译器会判断变量是否“逃逸”出函数。没逃逸的,即使你用了 new,也可能被优化到栈上,既安全又快。