本文共 4082 字,大约阅读时间需要 13 分钟。
深度探索 C++ 对象模型( 9 ) 介绍 当编译一个 C++ 程序时,计算机的内存被分成了 4 个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆( heap ) , 我们称堆是自由的内存区域,我们可以通过 new 和 delete 把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦 new 了它,必须 delete 它,否则程序将崩溃,这便是内存泄漏。( C# 已经通过内存托管解决了这一令人头疼的问题)。 C++ 通过 new 来分配内存, new 的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于 new 的知识。 正文 这一章主要是说 Runtime Semantics 执行期语义学。 这是我们平时写的程序片段: Matrix identity; // 一个全局对象 Main() { Matrix m1=identity; …… return 0; } 很常见的一个代码片段,雷神从来没有考虑过 identity 如何被构造,或者如何被销毁。因为它肯定在 Matrix m1=identity 之前就被构造出来了,并且在 main 函数结束前被销毁了。我们不用考虑这些问题,好象 C++ 就应该这样。但这本书是研究 C++ 底层机制的。既然我们在看这本书,说明我们希望了解 C++ 的编译器又做了那些大量的工作,使得我们可以这样使用对象。 在 C++ 程序中所有的全局对象都被放在 data segment 中,如果明确赋值,则对象以该值为初值,否则所配置到内存内容为 0 。也就是说,如果我们有以下定义 Int v1=1024; Int v2; 则 v1 和 v2 都被配置于 data segment , v1 值为 1024 , v2 值为 0 。(雷神在 VC6 环境用 MFC 编程时中发现如果 int v2;v2 的值不为 0 ,而是 -8 ,不知为什么?编译器造成的?)。 如果有一个全局对象,并且这个对象有构造函数和析构函数的话,它需要静态的初始化操作和内存释放工作, C++ 是一种跨平台的编程语言,因此它的编译器需要一种可以移植的静态初始化和内存释放的方法。下面便是它的策略。 1 、 为每一个需要静态初始化的档案产生一个 _sit() 函数,内带构造函数或内联的扩展。 2 、 为每一个需要静态的内存释放操作的文件中,产生一个 _std() 函数,内带析构函数或内联的扩展。 3 、 提供一个 _main() 函数,用来调用所有的 _sti() 函数,还有一个 exit() 函数调用所有的 _std() 函数。 侯
先生说: Sit 可以理解成 static initialization 的缩写。 Std 可以理解成 static deallocation 的缩写。 那么 main 函数会被编译器变成这样: Matrix identity; // 一个全局对象 Main() { _main();// 对所有的全局对象做 static initialization 动作。 Matrix m1=identity; …… exit();// 对所有的全局对象做 static deallocation 动作。 } 其中 _main() 会有一个对 identity 对象的静态初始化的 _sti 函数,象下面伪码这样: // matrix_c 是文件名编码 _identity 表示静态对象,这样能够保证向执行文件提供唯一的识别符号 _sti__matrix_c_identity() { identity.Matrix:: Matrix(); // 这就是静态初始化 } 相应的在 exit() 函数也会有一个 _std_matrix_c_identity(), 来进行 static deallocation 动作。 但是被静态初始化的对象有一些缺点,在使用异常时,对象不能被放置在 try 区段内。还有对象的相依顺序引出的复杂度,因此不建议使用需要静态初始化的全局对象。 局部静态对象在 C++ 底层机制是如何构造和在内存中销毁的呢? 1 、 导入一个临时对象用来保护局部静态对象的初始化操作。 2 、 第一次处理时,临时对象为 false ,于是构造函数被调用,然后临时对象被改为 true. 3 、 临时对象的 true 或者 false 便成为了判断对象是否被构造的标准。 4 、 根据判断的结果决定对象的析构函数是否执行。 如果一个类定义了构造函数或者析构函数,则当你定义了一个对象数组时,编译器会通过运行库将你的定义进行加工,例如 : point knots[10]; // 我们的定义 vec_new(&knots,sizeof(point),10,&point::point,0); // 编译器调用 vec_new() 操作。 下面给出 vec_new ()原型,不同的编译器会有差别。 void * vec_new ( void *array, // 数组的起始地址 size_t elem_size, // 每个对象的大小 int elem_count, // 数组元素个数 void(*constructor)(void*), void(*destructor)(void* ,char) ) 对于明显获得初值的元素, vec_new ()不再有必要,例如: point knots[10]={ Point(), //knots[0] Point(1.0,1.0,0.5), //knots[1] -1.0 //knots[2] }; 会被编译器转换成: //C++ 伪码 Point::Point(&knots[0]); Point::Point(&knots[1],1.0,1.0,0.5); Point::Point(&knots[2],-1.0,0.0,0.0); vec_new(&knots,sizeof(point),10,&point::point,0); // 剩下的元素,编译器调用 vec_new() 操作。 怎么样,很神奇吧。 当编译一个 C++ 程序时,计算机的内存被分成了 4 个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆( heap ) , 我们称堆是自由的内存区域,我们可以通过 new 和 delete 把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦 new 了它,必须 delete 它,否则程序将崩溃,这便是内存泄漏。( C# 已经通过内存托管解决了这一令人头疼的问题)。 C++ 通过 new 来分配内存, new 的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于 new 的知识,下面看看通过这本书,使我们能够更进一步的了解到些什么。 Point3d *origin=new Point3d; // 我们 new 了一个 Point3d 对象 编译器开始工作,上面的一行代码被转换成为下面的伪码: Point3d * origin; If(origin=_new(sizeof(Point3d))) { try{ origin=Point3d::Point3d(origin); } catch(…){ _delete(origin); throw; } } 而 delete origin; 会被转换成 ( 雷神将书上的代码改为 exception handling 情况 ) : if(origin!=0){ try{ Point3d::~Point3d(origin); _delete(origin); catch(…){ _delete(origin); // 不知对否? throw; } } 一般来说对于 new 的操作都直截了当,但语言要求每一次对 new 的调用都必须传回一个唯一的指针,解决这个问题的办法是,传回一个指针指向一个默认为 size=1 的内存区块,实际上是以标准的 C 的 malloc() 来完成。同样 delete 也是由标准 C 的 free() 来完成。原来如此。 最后这篇笔记再说说临时对象的问题。 T operator+(const T&,const T&); // 如果我们有一个函数 T a,b,c; // 以及三个对象: c=a+b; // 可能会导致临时对象产生。用来放置 a+b 的返回值。然后再由 T 的 copy constructor 把临时对象当作 c 的初值。也有可能直接由拷贝构造将 a+b 的值放到 c 中,这时便不需要临时对象。另外还有一种可能通过操作符的重载定义,经 named return value 优化也可以获得 c 对象。这三种方法结果一样,区别在于初始化的成本。对临时对象书上有很好的总结: 在某些环境下,有 processor 产生的临时对象是有必要的,也是比较方便的,这样的临时对象由编译器决定。 临时对象的销毁应该是对完整表达式求值过程的最后一个步骤。 因为临时对象是根据执行期语义有条件的产生,因此它的生命规则就显得很复杂。 C++ 标准要求凡含有表达式执行结果的临时对象,应该保留到对象的初始化操作完成为止。当然这样也会有例外,当一个临时对象被一个引用绑定时,对象将残留,直到被初始化的引用的生命结束,或者超出临时对象的作用域。 来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10294527/viewspace-126266/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/10294527/viewspace-126266/