设计排版网站,网站建设方面的外文,网店代运营是做什么的,黄山旅游景点作者 | 码农的荒岛求生来源 | 码农的荒岛求生有伙伴问到底是从栈上分配内存快还是从堆上分配内存快#xff0c;这是个比较基础的问题#xff0c;今天就来聊一聊。栈区的内存申请与释放毫无疑问#xff0c;显然从栈上分配内存更快#xff0c;因为从栈上分配内存仅仅就是栈指… 作者 | 码农的荒岛求生来源 | 码农的荒岛求生有伙伴问到底是从栈上分配内存快还是从堆上分配内存快这是个比较基础的问题今天就来聊一聊。栈区的内存申请与释放毫无疑问显然从栈上分配内存更快因为从栈上分配内存仅仅就是栈指针的移动而已这是什么意思呢什么叫做“栈指针的移动”以x86平台为例在栈上分配内存是怎样实现的呢很简单就一行指令sub $0x40,%rsp这行代码就叫做“栈指针的移动”其本质就是这张图很简单寄存器esp中保存的是当前栈的栈顶地址由于栈的增长方向是从高地址到低地址因此增大栈时需要将栈指针向下移动即sub指令的作用这条指令将栈顶指针向下移动了64字节(0x40)因此可以说在栈上分配了64字节。可以看到在栈上分配内存其实非常非常简单简单到就只有一条机器指令。而栈区的内存释放也非常简单也是只需要一条机器指令leaveleave指令的作用是将栈基址赋值给esp这样栈指针指向上一个栈帧的栈顶然后pop出ebp这样ebp就指向上一个栈帧的栈底看到了吧执行完leave指令后ebp以及esp就指向上一个栈帧了这就相当于栈帧的弹出pop这样stack 1占用的内存就无效了没有任何用处了显然这就是我们常说的内存回收因此简单的一条leave指令就可以回收掉栈区中的内存。接下来我们看到堆区的内存申请与释放。堆区的内存申请与释放与栈区分配内存相对的是堆内存分配堆区分配内存有多复杂呢在堆区上申请与释放内存是一个相对复杂的过程因为堆本身是需要程序员(内存分配器实现者)自己管理的而栈是编译器来维护的堆区的维护同样涉及内存的分配与释放但这里的内存分配与释放显然不会像栈区那样简单一句话这里是按需进行内存的分配与释放本质在于堆区中每一块被分配出去的内存其生命周期都不一样这是由程序员决定的我倾向于把内存动态分配释放想象成去停车场找停车位。这显然会让问题复杂起来我们必须小心的维护哪些内存是已经分配出去的以及哪些是空闲的、该怎样找到一块空闲的内存、该怎样回收程序员不需要的内存块、同时还不能有严重的内存碎片问题栈区分配释放内存都无需关心这些问题于此同时当堆区内存空间不足时还需要扩大堆区等等这些都使得在堆区申请内存要比在栈区分配内存复杂的多。说了这么多那么在堆区上申请内存要比在栈上申请内存慢多少呢接下来我们写段代码实验一下。代码void test_on_stack() {int a 10;
}void test_on_heap() {int* a (int*)malloc(sizeof(int));*a 10;free(a);
}void test() {auto begin GetTimeStampInUs();for (int i 0; i 100000000; i) {test_on_stack();}couttest on stack ((GetTimeStampInUs() - begin) / 1000000.0)endl;begin GetTimeStampInUs();for (int i 0; i 100000000; i) {test_on_heap();}couttest on heap ((GetTimeStampInUs() - begin) / 1000000.0)endl;
}这段代码非常简单这里有两个函数test_on_stack函数中定义一个局部变量这就是从栈上申请一个整数大小的内存空间test_on_heap函数从堆上申请一个整数大小的内存空间然后我们在测试函数中分别调用这两个函数每一个调用1亿次记录下需要运行的时间得到的测试结果为test on stack 0.191008
test on heap 20.0215可以看到在栈上总耗时只有大概0.2s而在堆上分配的耗时为20s相差百倍。值得注意的是这里在编译程序时没有开启编译优化开启编译优化后的耗时是这样的test on stack 0.033521
test on heap 0.039294可以看到相差无几可这是为什么呢显然从常理推断在栈上分配要更快一些问题会出在哪里呢既然我们开启了编译优化那是不是优化后的代码运行的更快了呢我们来看下编译优化后生成的指令都有啥test_on_stackv:400f85: 55 push %rbp400f86: 48 89 e5 mov %rsp,%rbp400f89: 5d pop %rbp400f8a: c3 retqtest_on_heapv:400f8b: 55 push %rbp400f8c: 48 89 e5 mov %rsp,%rbp400f8f: 5d pop %rbp400f90: c3 retq啊哈编译器实在是太聪明了它显然注意到这两个函数中的代码实际上啥也没干即使我们还专门为变量a赋值为了10但后续我们根本就没有用到变量a因此编译器给我们生成了一个空函数上面这些机器指令实际上对应一个空函数。小风哥反复在这里添加代码都没有骗过编译器我试图加大变量a赋值的复杂度编译器依然很聪明的生成了一个空函数反正我是没有试出来可见现代编译器是足够智能的生成的机器指令效率很高关于该怎样写出一个更好的benchmark从而让我们可以看到在开启编译优化的情况下这两种内存分配方式的对比欢迎任何对此有心得或者对编译优化有心得的同学留言。最后让我们来看看这两种内存分配方式的定位。栈内存与堆内存的差异首先我们必须意识到栈是一种先进后出的结构栈区会随着函数调用层级的增加而增大而随着函数调用完成而减少因此栈是无需任何“管理”的与此同时由于栈的这种性质在栈上申请的内存其生命周期是和函数绑定在一起当函数调用完成后其占用的栈帧内存将无效且栈的大小是有限的你不能在栈上申请过多内存就像这样一段C代码void test() {int b[10000000];b[1000000] 10;
}这段代码运行起来后会core掉原因就在于栈区大小是非常有限的在栈上分配一大块数据会让栈撑爆掉这就是所谓的Stack Overflow额。。。不好意思图放错了应该是这个Stack Overflow不好意思又放错了总之你懂得。而堆则不同在堆上分配的内存其生命周期是受程序员控制的程序员决定什么时候申请内存什么时候释放内存因此堆是必须被管理起来的堆区是一片很广阔的区域堆区空间不足时会向操作系统请求扩大堆区从而获得更多地址空间。当然堆区在给程序员更大灵活性的同时需要程序员确保内存在不被使用时释放掉否则会内存泄漏在栈上申请内存则不存这个问题。总结栈区是自动管理的堆区是手动管理的显然在栈区上分配内存要比在堆区上更快当在栈区上申请的内存使用场景有限程序员申请内存时还要更多的依靠堆区但是在栈区申请的内存满足要求的情况我个人更倾向于使用栈区内存。希望这篇文章对大家理解堆区栈区有所帮助。往期推荐Redis 缓存击穿失效、缓存穿透、缓存雪崩怎么解决如果被问到分布式锁应该怎样回答性能突出的 Redis 是咋使用 epoll 的Java 底层知识什么是 “桥接方法” 点分享点收藏点点赞点在看