您的位置:首页 > 编程语言 > C语言/C++

C++ 内存分配学习笔记

2011-08-18 10:27 239 查看
C++将内存划分为三个逻辑区域:栈、堆和静态存储区。其中的对象我们分别称作栈对象、堆对象和静态对象。

其中,栈中主要保存局部变量和对象,栈中变量的对象的生成有程序建立,但是删除则是有编译器自动完成,对于系统自定义类型,系统调用类型对应的析构函数(如果有的话),对于某些基本类型,系统不会删除该变量,对于用户自定义类型,编译器将自动调用类对应的析构函数删除该对象。

堆、又叫做自由存储区,主要保存动态变量,它在程序执行的过程中动态分配的。在C++中,所有的堆内存的申请和释放都是由程序员完成,如果处理不好就容易造成内存泄漏,例如多次释放一个指针所指的对象,使用悬浮指针等等。对于堆内存的一个好的处理方法是用管理资源分配,例如使用auto_ptr类来完成动态变量的申请及释放。这样不但能避免由于程序员的失误忘记使用delete删除new申请的动态内存,还可以避免在程序出现异常的时候无法执行delete操作引起的内存问题。

静态存储区。所有的静态对象、全局对象都于静态存储区分配。关于全局对象,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的的构造及初始化工作。而在main()函数结束之前,会调用由编译器生成的exit函数,来释放所有的全局对象。

例如如下代码:
void main(void)
{
 … …// 显式代码
}
经过编译器编译后生成如下的代码:

void main(void)
{
  _main(); //隐式代码,由编译器产生,用以构造所有全局对象
  … … // 显式代码
  … …
  exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
}


还有一种静态对象,那就是它作为class的静态成员。考虑这种情况时,就牵涉了一些较复杂的问题。

第一个问题是class的静态成员对象的生命期,class的静态成员对象随着第一个class object的产生而产生,在整个程序结束时消亡。也就是有这样的情况存在,在程序中我们定义了一个class,该类中有一个静态对象作为成员,但是在程序执行过程中,如果我们没有创建任何一个该class object,那么也就不会产生该class所包含的那个静态对象。还有,如果创建了多个class object,那么所有这些object都共享那个静态对象成员(包括派生类对象会与基类对象一起共享基类对象的静态变量)。

三种内存的比较:

栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创建速度一般较堆对象快,因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。但是要注意的是,通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。

  堆对象,其产生时刻和销毁时刻都要程序员精确定义,也就是说,程序员对堆对象的生命具有完全的控制权。我们常常需要这样的对象,比如,我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个时候创建一个堆对象无疑是良好的选择,然后在各个函数之间传递这个堆对象的指针,便可以实现对该对象的共享。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存。

  接下来看看static对象。

  首先是全局对象。全局对象为类间通信和函数间通信提供了一种最简单的方式,虽然这种方式并不优雅。一般而言,在完全的面向对象语言中,是不存在全局对象的,比如C#,因为全局对象意味着不安全和高耦合,在程序中过多地使用全局对象将大大降低程序的健壮性、稳定性、可维护性和可复用性。C++也完全可以剔除全局对象,但是最终没有,我想原因之一是为了兼容C。

  其次是类的静态成员,上面已经提到,基类及其派生类的所有对象都共享这个静态成员对象,所以当需要在这些class之间或这些class objects之间进行数据共享或通信时,这样的静态成员无疑是很好的选择。

  接着是静态局部对象,主要可用于保存该对象所在函数被屡次调用期间的中间状态,其中一个最显着的例子就是递归函数,我们都知道递归函数是自己调用自己的函数,如果在递归函数中定义一个nonstatic局部对象,那么当递归次数相当大时,所产生的开销也是巨大的。这是因为nonstatic局部对象是栈对象,每递归调用一次,就会产生一个这样的对象,每返回一次,就会释放这个对象,而且,这样的对象只局限于当前调用层,对于更深入的嵌套层和更浅露的外层,都是不可见的。每个层都有自己的局部对象和参数。

  在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息