1.C++程序内存布局
在C++中,内存分成4个区,从低地址到高地址分别是常量区、全局数据区、堆区、栈区。栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
2.堆和栈的区别
(1). 管理方式不同;栈申请和释放由编译器管理,而堆的管理由程序员控制,所有容易产生内存泄露
(2). 空间大小不同;栈一般很小,可以设置1M左右,堆内存基本没有限制。
(3). 能否产生碎片不同;栈不会产生内存碎片,而堆内存由于频繁的new/delete会产生内存碎片。
(4). 生长方向不同;栈从上往下高地址向低地址生产,堆由低地址向高地址生长
(5). 分配方式不同;栈由编译器分配,堆由程序员动态分配
(6). 分配效率不同;堆比栈的分配效率低得多
堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界)。
3.内存操作错误及其对策
(1). 内存分配未成功,却使用了它。编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。
(2). 内存分配虽然成功,但是尚未初始化就引用它。犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值。
(3). 内存分配成功并且已经初始化,但操作越过了内存的边界。例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。
(4). 忘记了释放内存,造成内存泄露。含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。
(5). 释放了内存却继续使用它。
【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
【规则4】动态内存的申请与释放必须配对,防止内存泄漏。
【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
4.malloc/free和new/delete的区别?
malloc与free是C/C++语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。注意用new创建对象数组时,只能使用对象的无参构造函数。
5.
6.
7
8.
程序通过系统的内存分配一次性申请适当大小的内存作为一个内存池,之后应用程序自己对内存的分配和释放则可以通过这个内存池来完成。只有当内存池需要动态扩张时,才需要系统再调用内存分配函数,其它时间对内存的一切掌握都在应用程序的掌握之中。
从线程安全角度,内存池可以分为单线程内存池和多线程池
单线程内存池:单线程内存池在整个生命周期只被一个线程使用,因而不需要考虑互斥问题
多线程内存池:多线程内存池可能被多个线程共享,因此则需要在每次分配和释放内存时加锁。
一般而言,单线程内存池性能更好,而多线程内存池适用范围更广。
从内存池可分配内存单元来分:固定内存池和可变内存池
固定内存池:指应用程序每次从内存池中分配出的内存单元大小事先已经确定,是固定不变的
可变内存池:每次分配的内存单元大小可以按需变化
可变内存池应用范围更广,而性能比固定内存池要低。