内存对象笔记

 原文:

http://blog.sina.com.cn/s/blog_5c39a0890100ct10.html

 

C++将内存划分为三个逻辑区域:堆、栈和静态存储区。位于它们之中的对象分别为堆对象,栈对象以及静态对象。

 

·栈,一般用于存放局部变量或对象。它的生命期是从定义点开始,当所在函数返回时,生命结束。几乎所

  有的临时对象都是栈对象。

·栈对象是在适当的时候创建,然后在适当的时候自动释放(第一,在其生命期结束的时候自动释放; 第

  二,在其所在的函数发生异常的时候自动释放。)

·栈对象,自动释放时,会调用它自己的析构函数。

·Type fun(Type object);

  这个函数至少产生两个临时对象:

  - 参数是按值传递的,所以会调用拷贝构造函数生成一个临时对象;

  - 还有这个函数是值返回的。不考虑返回值优化(NRV),那么也会产生一个临时对象。 

·生成某些临时对象的时间和空间的开销可能是很大的,对于“大”对象最好用 const 引用传递代替按值

  进行函数参数传递。

·堆,又叫自由存储区,它是在程序执行的过程中动态分配的,最大的特性就是动态性。

·堆对象的创建和销毁都要由程序员负责。

·如果分配了堆对象,却忘记了释放,就会产生内存泄漏;

  如果已释放了对象,却没有将相应的指针置为 NULL ,该指针就是所谓的“悬挂指针”,再度使用此指针

  时,就会出现非法访问,严重时就导致程序崩溃。

静态存储区

·所有的静态对象、全局对象都于静态存储区分配。

·全局对象,是在 main() 函数执行前就分配好了的。

·其实,在 main() 函数中的显示代码执行之前,会调用一个由编译器生成的 _main() 函数,而 _main()

  函数会进行所有全局对象的的构造及初始化工作。而在 main() 函数结束之前,会调用由编译器生成的

  exit 函数,来释放所有的全局对象。比如下面的代码:

 

void main(void)
{
 … …// 显式代码
}

  实际上,被转化成这样:

 

 

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

 


  知道了这个之后,便可以由此引出一些技巧,如,假设我们要在 main() 函数执行之前做某些准备工作,

  那么我们可以将这些准备工作写到一个自定义的全局对象的构造函数中,这样,在 main() 函数的显式代

  码执行之前,这个全局对象的构造函数会被调用,执行预期的动作,这样就达到了我们的目的。

·局部静态对象通常也是在函数中定义的,它的生命期是从第一次执行到该静态对象的声明代码时,产生该

  静态局部对象,直到整个程序结束时,才销毁该对象。

·还有一种静态对象,那就是它作为 class 的静态成员。考虑这种情况时,就牵涉了一些较复杂的问题。
  - 第一个问题是class的静态成员对象的生命期,class 的静态成员对象随着第一个 class object 的产

    生而产生,在整个程序结束时消亡。也就是有这样的情况存在,在程序中我们定义了一个 class ,该

    类中有一个静态对象作为成员,但在程序执行过程中,如果我们没有创建任何一个该 class object ,

    那么也就不会产生该 class 所包含的那个静态对象。还有,如果创建了多个 class object ,那么所

    有这些 object 都共享那个静态对象成员。

  - 第二个问题是,当出现下列情况时:

 

class Base
{
 public:
  static Type s_object ;
}
class Derived1 : public Base / / 公共继承
{
 … …// other data
}
class Derived2 : public Base / / 公共继承
{
 … …// other data
}

Base example ;
Derivde1 example1 ;
Derivde2 example2 ;
example.s_object = …… ;
example1.s_object = …… ;
example2.s_object = …… ;

 


  请注意上面标为黑体的三条语句,它们所访问的 s_object 是同一个对象吗?答案是肯定的,它们的确

    是指向同一个对象。当一个类比如 Derived1 ,从另一个类比如 Base 继承时,那么,可以看作一个

    Derived1 对象中含有一个 Base 型的对象,这就是一个 subobject。一个 Derived1 对象的大致内存

    布局如下:

 

 

 

 

一个 Base 型的 subobject

 

   

 

Dervied1 的其他成员

 

     一个 Dervied1 对象

 

                                                                  
  当我们将一个 Derived1 型的对象传给一个接受非引用 Base 型参数的函数时会发生切割,即仅仅取出

    了 Derived1 型的对象中的 subobject ,而忽略了所有 Derived1 自定义的其它数据成员,然后将这

    个 subobject 传递给函数(实际上,函数中使用的是这个 subobject 的拷贝)。

  所有继承 Base 类的派生类的对象都含有一个 Base 型的 subobject(这是能用 Base 型指针指向一个

    Derived1 对象的关键所在,自然也是多态的关键了),而所有的 subobject 和所有 Base 型的对象都

    共用同一个 s_object 对象,自然,从 Base 类派生的整个继承体系中的类的实例都会共用同一个

    s_object 对象了。

 

 

三种内存对象的比较
·栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创

  建速度一般较堆对象快,因为分配堆对象时,会调用 operator new 操作,operator new 会采用某种内

  存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指

  针就可以了。但是要注意的是,通常栈空间容量比较小,一般是 1MB~2MB ,所以体积比较大的对象不适

  合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空

  间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。

·堆对象。比如,我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个

  时候创建一个堆对象无疑是良好的选择,然后在各个函数之间传递这个堆对象的指针,便可以实现对该对

  象的共享。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成

  新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存。

·静态对象

  - 首先是全局对象,全局对象为类间通信和函数间通信提供了一种最简单的方式,虽然这种方式并不优

    雅。一般而言,在完全的面向对象语言中,是不存在全局对象的,比如 C#,因为全局对象意味着不安

    全和高耦合,在程序中过多地使用全局对象将大大降低程序的健壮性、稳定性、可维护性和可复用性。

    C++ 也完全可以剔除全局对象,但是最终没有,我想原因之一是为了兼容 C。

  - 其次是类的静态成员,上面已经提到,基类及其派生类的所有对象都共享这个静态成员对象,所以当需

    要在这些 class 之间或这些 class objects 之间进行数据共享或通信时,这样的静态成员无疑是很好

    的选择。

  - 接着是静态局部对象主要可用于保存该对象所在函数被屡次调用期间的中间状态,其中一个最显著的

    例子就是递归函数,如果在递归函数中定义一个 nonstatic 局部对象,那么当递归次数相当大时,所

    产生的开销也是巨大的。因为 nonstatic 局部对象是栈对象,每递归调用一次,就会产生一个这样的

    对象,每返回一次,就会释放这个对象,而且,这样的对象只局限于当前调用层,对于更深入的嵌套层

    和更浅露的外层,都是不可见的。每个层都有自己的局部对象和参数。

    在递归函数设计中,可以使用 static 对象替代 nonstatic 局部对象(即栈对象),这不仅可以减少

    每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的

    中间状态,并且可为各个调用层所访问。

·某块内存中的数据是不变的,而类型就是我们戴上的眼镜,当我们戴上一种眼镜后,我们就会用对应的类

  型来解释内存中的数据,这样不同的解释就得到了不同的信息。

  所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据。
  另外要提醒的是,不同的编译器对对象的成员数据的布局安排可能是不一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值