如果你是个C++程序员,当面试官问你内存管理的时候,通常他期望你能回答:

“临时变量在和函数调用发生在stack,new、malloc等需要使用heap,另外全局变量和函数等等分别在code、data、bss等地方。”。

大部分面试官听到这样的答案会给你一个满意的微笑。 但也不排除少部分硬核面试官会继续深入问你。因为实际上,内存管理是一块非常庞杂的内容,可以往深处细问, 它发生在一个程序执行的多个层次上(库、进程、内核)。 同时内存管理也是一个重要的问题。 本文将以C\C++程序为例讲讲内存是如何在多个抽象层次上分配的。

内存由谁来分配?

接口之前

  • 当你在c++里面使用 auto value = vector<int> (10,0); 意味着你通过STL新建了一个 vector对象,里面放了10个值为0的int整数。STL的源码里说,内存分配是由Allocator来做的。所以可以说这个对象使用的内存是有Allocator分配的。 不过如果继续往下看,Allocator只是个中间商,它其实只是用了new来获取内存,不过它也有自己的一些管理手段比如内存池来提高内存分配、释放的效率。

  • new和delete呢,是C++里面的动态内存分配的关键字,它可以根据类型分配大小,并调用构造/析构函数,不过更硬核的C程序员会使用maloc与free来管理内存。

接口之后

  • 那么malloc就是内存分配的主角了吗?是也不是,如果作为C\C++程序员,malloc、calloc等*lloc系列函数再加上free就已经是能往底下看到的最深的地方了。 但是这段标题是“接口之后”,讲的是接口之后的故事,那么我们就不该放过开源的glibc,可以再看看它是怎么实现的。 glibc的链接送上,使用的是Doug Lea 的malloc版本 glibc code on github 不过直接看代码有点难懂,不过没关系,里面有大量注释阐述了malloc的指导思想,并且有这篇高屋建瓴的文章Doug Lea 在oswego网站上的文章。 那就不难发现,malloc使用mmap和brk这两个系统调用来实现的动态内存管理。 而且malloc自己会管理heap上的chunk,并且会在mapping area申请大于128K的内存。以综合考虑系统调用的次数和内存碎片的大小。

  • 再深入一点,一旦发生mmap和brk等系统调用,程序就从用户态转变为内核态,接下来就是内核来接管的内容,内核处在和进程的不同虚拟空间, 内核会把内存分页成4k大小的页框,然后分配给不同的地址空间。 内核使用buddy system和slab来管理分配动态内存,并且会实施页框回收来释放内存或使用Swap空间。

小结

一路下来,我们发现C++/C程序的内存管理是由用户库(STL库)、C++标准、C标准库、内核在不同级别上管理的。

接下来的文章会介绍每一部分的内容都在做什么,并附上一些代码,不过不一定都是原创,可能会引用许多开源代码和其他文献。

建议的参考文献:

  • 基本C++用法:C++ Primer
  • 关于 Operator new: http://www.cplusplus.com/reference/new/operator%20new/
  • STL的allocator:STL源码剖析
  • malloc 的背后:http://gee.cs.oswego.edu/dl/html/malloc.html
  • 内核的内存管理:深入理解linux内核