这是我去腾讯面试的时候遇到的一个问题——malloc()是如何申请内存的?c++
c++ 内存获取和释放 new/delete,new[]/delete[]面试
c 内存获取和释放 malloc/free, calloc/realloc数组
上述8个函数/操做符是c/c++语言里经常使用来作动态内存的申请和释放的,要理解这些接口,大概须要安全
下面几个维度的了解:cookie
1. 了解OS的进程空间模型,一个进程的地址空间,通常划分为内核区、用户区,用户区又划分为栈区、堆区、数据区、代码区。函数
这里的‘堆区’,‘栈区’,‘数据区’,‘内核区’,其实就是一个虚拟地址区间,动态内存最终都是从OS的'堆区'上获取的。google
2. brk、mmap 系统调用指针
brk系统调用,可让进程的堆指针增加必定的大小,逻辑上消耗掉一块本进程的虚拟地址区间,malloc向OS获取的内存大小比较小时,将直接经过brk调用获取虚拟地址,结果是将本进程的brk指针推高。对象
mmap系统调用,可让进程的虚拟地址区间里切分出一块指定大小的虚拟地址区间vma_struct,并返回给用户态进程,被mmap映射返回的虚拟地址,逻辑上被消耗了,直到用户进程调用munmap,才回收回来。malloc向系统获取比较大的内存时,会经过mmap直接映射一块虚拟地址区间。mmap系统调用用处很是多,好比一个进程的全部动态库文件.so的加载,都须要经过mmap系统调用映射指定大小的虚拟地址区间,而后将.so代码动态映射到这些区域,以供进程其余部分代码访问;另外,多进程通信,也可使用mmap,这块另开文章详解。继承
不管是brk仍是mmap返回的都是虚拟地址,在第一次访问这块地址的时候,会触发缺页异常,而后内核为这块虚拟地址申请并映射物理页框,创建页表映射关系,后续对该区间虚拟地址的访问,经过页表获取物理地址,而后就能够在物理内存上读写了。
3. malloc/free 是libc库函数
malloc/free是 libc实现的库函数,主要实现了一套内存管理机制,当其管理的内存不够时,经过brk/mmap等系统调用向内核申请进程的虚拟地址区间,若是其维护的内存能知足malloc调用,则直接返回,free时会将地址块返回空闲链表。
malloc(size) 的时候,这个函数会多分配一块空间,用于保存size变量,free的时候,直接经过指针前移必定大小,就能够获取malloc时保存的size变量,从而free只须要一个指针做为参数就能够了calloc 库函数至关于 malloc + memset(0)
除了libc自带的动态内存管理库malloc, 有时候还可使用其余的内存管理库替换,好比使用google实现的tcmalloc ,只须要编译进程时连接上 tcmalloc的静态库并包含响应头文件,就能够透明地使用tcmalloc 了,与libc 的malloc相比, tcmalloc 在内存管理上有不少改进,效率和安全性更好。
4. new/new[]/delete/delete[]
new/delete 是c++ 内置的运算符,至关于加强版的malloc/free. c++是兼容c的,通常来讲,一样功能的库,c++会在安全性和功能性方面
比c库作更多工做。动态内存管理这块也同样。
new的实现会调用malloc,对于基本类型变量,它只是增长了一个cookie结构, 好比须要new的对象大小是 object_size, 则事实上调用 malloc 的参数是 object_size + cookie, 这个cookie 结构存放的信息包括对象大小,对象先后会包含两个用于检测内存溢出的变量,全部new申请的cookie块会连接成双向链表。因为内置了内存溢出检测,因此比malloc更安全。
对于自定义类型,new会先申请上述的大小空间,而后调用自定义类型的构造函数,对object所在空间进行构造。c++比c强大的一个方面
就是c++编译器能够自动作构造和析构,new运算符会自动计算须要的空间大小,而后根据类型本身调用构造函数,若是存在子类型对象,或者存在继承的基类型,new都会自动调用子类型的构造函数和基类型的构造函数完成构造。一样,delete 操做符根据cookie的size知道object的大小,若是是自定义类型,会调用析构函数对object所在空间进行析构,若是有子类型或继承,自动调用子类型和基类型的析构函数,而后将cookie块从双向链表摘除,最后调用 free_dbg 释放。
new[] 和delete[]是另外两个操做符,用于数组类型的动态内存获取和释放,实现过程相似new/delete