malloc的申请方式
1.具有长生命周期的大内存将使用mmap分配。
2.对于短生命周期的内存分配将使用brk系统调用。
3.对于小内存块的释放将返回到bin数组下,大内存(使用mmap分配的)将直接返回给操作系统。
4.小内存块的合并(切割)仅仅在malloc和free的时候,并且合并(切割)以后也不一定返回给操作系统
5.设置了若干阀值来避免内存暴增现象。
6.为了支持多线程,多个线程可以从同一个分配区分配内存,但是会使用锁来保证线程安全。同时为了优化算法,在出现内存不够的时候会重新创建一个分配区。
一. malloc的底层实现原理ptmalloc ,tcmalloc、
【1】少于128K的内存malloc int main() { void *p=malloc(4); free(p); return 0; } |
【2】大于128K的内存malloc int main() { void *p=malloc(1024*128); free(p); return 0; } |
在Linux上用gcc编译代码【1】,通过strace命令跟踪可执行程序(#strace ./a.out)
当你通过malloc申请小于128K个字节的内存时,linux内核上是通过do_brk这个系统调用来实现的,而且你会发现,你通过malloc只想申请4个字节的内存,内核却把brk指针
移动了好几个内存页面,当然malloc只会给你返回4字节,其他的大片内存他就要自己管理起来,下次你再申请时就不用深入内核了,一切为了效率!那么想知道malloc是怎
么把剩余的堆内存管理起来的呢?对了,这就是ptmalloc的实现原理,通过bins和chunk机制来实现内存的管理机制,你还可以看到,通过brk系统调用申请的内存在应用程序
free时,并没有归还给操作系统,那归还给谁了?答案是归还给了ptmalloc,详细的内存管理机制非常精彩
同时在Linux上编译代码【2】,大家会发现申请>=128K的堆内存时,Linux内核上是通过mmap函数申请的,在free时是对应的内核调用函数munmap也被调用了
// 深入理解do_brk,mmap,munmap;函数
在c和c++中的动态内存分配
【1】malloc和new的区别?
(1)malloc 申请内存在堆上,new在自由存储区(堆和静态存储区)申请内存并初始化
(2)Malloc是函数 ,new是关键字(可自己实现重载)
(3)Malloc需要具体的字节;new只需要对象名
(4)New和delete会调用对象的构造和析构
(5)返回值问题,malloc成功返回void*失败返回NULL ;new成功返回对象的指针,失败抛出异常(也支持返回NULL,为了兼容c,但一般不用)你们可以看看底层实现。
Malloc扩容可以用relloc但new不行,若当前申请的内存获取不足时,new有一个set_new_handler机制,循环尝试压缩内存,若超出时间限制抛出异常,而malloc失败则没有这种机制,直接返回NULL;
【2】new和new[]的区别?
(1)new生成单个对象的空间,new[]生成数组
【3】堆和栈的区别?
1、管理方式不同;
2、空间大小不同;
3、能否产生碎片不同;
4、生长方向不同;
5、分配方式不同;
6、分配效率不同;
管理方式: 栈:系统开辟,系统释放
堆:手动开辟,手动释放
空间大小:一般来讲在32位系统下,堆内存可以达到2G的空间,栈空间大小是1M。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出
生长方向:堆:生长方向向上,也就是向着内存地址增加的方向;
栈:生长方向向下,是向着内存地址减小的方向增长。
分配方式:堆:都是动态分配的,没有静态分配的堆
栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。
【4】有哪些内存泄漏,内存非法访问的操作?怎么定位?怎么解决?
free了空指针,free了两次相同内存,数组越界
【5】看完用户空间的内存分配,那么内核是怎么做的?伙伴系统的原理是怎么样的?Slab分配器知道吗?