Inside the C++ Object Model 学习笔记 第六章 执行期语义学


 

 

最近比较郁闷 不想学习 所以写点读书笔记 先写我觉得比较通俗易懂的第六章 执行期语义学 现在开始

这一章,我觉得主要讲了三个问题。

1,执行期 全局变量 局部静态对象 以及对象数组在编译器中都是怎样活来死去的。

2new and delete是怎样让对象生死的。

3,临时对象的生死

这本书之所以叫Inside the C++ object Model , 我觉得主要是因为它关乎编译器,内存的处理行为,主要那些什么变量啊,对象啊你可以把他们都当做object来处理。

一、对象的构造和解构

当编译一个C++程序时,计算机的内存被分成了4个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(heap,我们称堆是自由的内存区域,我们可以通过newdelete 把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦new了它,必须delete 它,否则程序将崩溃,这便是内存泄漏C++通过new来分配内存,new的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于new的知识,下面看看通过这本书,使我们能够更进一步的了解到些什么。

对于一个普通的该函数,他的constructordestructor安插都如我们的预期

// C++伪代

{

Point point;

//point.Point::Point()

//point.Point::~Point()

}但是如果我们的程序或函数中如果有多个离开点

{

Point point;

//constrcutor 刚说过

switch(int(Point.x()))

{ case -1:

//operatror()

//destructor为什么是这里 请看下边

return;

case 0:

....

}


我们的析构函数就会在离开点之前进行调用,不然结果你懂得。所以,我们会把object尽可能的放置在使用它的那个区域段的附近,这样最可以减少不必要的对象产生操作和摧毁操作。

二、各种对象的活来死去

书上的例子

Matrix identity; //一个全局对象
Main()
{
Matrix m1=identity;
……
return 0;
}
没想过,它是怎么构造的,什么时候死去的。

对于这个identity 我以前从它肯定在Matrix m1=identity之前就被构造出来了,并且在main函数结束前被销毁了。这个大家都懂,这本书主要介绍的是编译器做了什么,能让它跨平台的工作。

C++程序中所有的全局对象都被放在data segment中,如果明确赋值,则对象以该值为初值,否则所配置到内存内容为0。也就是说,如果我们有以下定义
Int v1=1024;
Int v2;
v1v2都被配置于data segmentv1值为1024v2值为0。这是书上讲的,可是有的时候v2不是0哦,可能是编译器的差别吧。

如果有一个全局对象,并且这个对象有构造函数和析构函数的话,它需要静态的初始化操作和内存释放工作,C++是一种跨平台的编程语言,因此它的编译器需要一种可以移植的静态初始化和内存释放的方法。下面便是它的策略。
1
为每一个需要静态初始化的档案产生一个_sit()函数,内带构造函数或内联的扩展。
2
为每一个需要静态的内存释放操作的文件中,产生一个_std()函数,内带析构函数或内联的扩展。
3
提供一个_main()函数,用来调用所有的_sti()函数,还有一个exit()函数调用所有的_std()函数。
这三个函数静态的搞定一个全局变量
那么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便成为了判断对象是否被构造的标准,如果是true,就是知道已经创建好了,析构函数可以将它消灭了。
4
根据判断的结果决定对象的析构函数是否执行。

书上有句话说:我们没有办法在静态的内存中释放函数中存取它(临时对象),我不太理解,是不是就是说他是临时对象,所以无法的静态存取呢?它在栈区,不在静态存储区就不能静态存取是么?不知道我的想法是不是对的

定义了一个对象数组时,编译器会通过运行库将你的定义进行加工,例如:
point knots[10]; //
我们的定义,请注意,这个是书上的例子,下边之所以能够进行下去的先前条件是这个Point既没有一个constructor也没有个destructor,有个default constructor(但凭我的印象 记得好像是trival的了),这个destructor就轮流的进行创建了。
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()操作。
怎么样,很神奇吧。
这里强调一下哦 这个vec_new()可不是随便调用的哦, 要满足一些条件哦。

书中还讲到了default constructor和数组的关系,没太看懂到底要说什么,觉得不重要,对。我没看懂的,都是不重要的,哈。

三、newdelete

对于一个简单的new操作。看看编译器是怎么做的

int *pi = new int(5);

总共分两步。

第一步,不是把冰箱门打开 如下所示:

int *pi = _new(sizeof(int));

适当的调用new运算符的函数实体,配置所需要的内存。

第二步,当然 设定初值

*pi = 5;

进一步,我没还需要看一下内存分配好了没,别没有地方,就往里放东西。

int *pi = new int(5);

判断下:

int *pi;

if(pi = +_new(sizeof(int)))

*pi = 5;

delet 操作看是怎么做的

delete pi;

这样做 我懂 你懂

if(pi !=0)

_delete(pi);

书里还说了,delete之后对pi的参考操作就可能不正确了,但pi仍然可以正常使用,我觉得大家都该明白什么意思,不再啰嗦。

上面说的是普通的new一个内置类型,下面就是主要的new object

Point3d *origin=new Point3d; //我们new 了一个Point3d对象,这里它是一个子类,继承与Point等等,以后会继续说

编译器开始工作,上面的一行代码被转换成为下面的伪码:
Point3d * origin;
If(origin=_new(sizeof(Point3d)))
{
try{
origin=Point3d::Point3d(origin);//
注意这里调用了 copy constructor
}
catch(…){
_delete(origin);
throw;
}
}
delete origin;
会被转换成
if(origin!=0){
try{
Point3d::~Point3d(origin);
_delete(origin);
catch(…){
_delete(origin); //
不知对否?
throw;
}
}
一般来说对于new的操作都直截了当,但语言要求每一次对new的调用都必须传回一个唯一的指针,解决这个问题的办法是,传回一个指针指向一个默认为 size=1的内存区块,实际上是以标准的Cmalloc()来完成。同样delete也是由标准Cfree()来完成。原来如此。
对于数组的new,下面看下

int p_array = new int[5];

这个不会调用vex_new() O, 看看前边就知道了。倒是new运算会被调用

int *p_array = (int*)_new(5*sizeof(int));

如果class 配置一个default constructor 那么某些版本的vec_new就会被调用的。

书上还讲了一下各个编译器之间的差异,对于delete是否返回申请的申请元素数目的差异,我觉得没必要搞懂。就没看。知道delete[] p_array;

delete[array_size] p_array;

都是可以的就行了, 我对析构函数怎么做,一直不怎么感冒。因为我都用的鸵鸟政策,反正释放了,到底怎么弄的,就那样吧。

四、临时对象

T operator+(const T&,const T&); //如果我们有一个函数
T a,b,c; //
以及三个对象:
c=a+b;
//
可能会导致临时对象产生。用来放置a+b的返回值。这要看你用的什么编译器,你懂的。

然后再由 Tcopy constructor把临时对象当作c的初值。也有可能直接由拷贝构造将a+b的值放到c中,这时便不需要临时对象。另外还有一种可能通过操作符的重载定义,经named return value优化也可以获得c对象。这三种方法结果一样,区别在于初始化的成本。对临时对象书上有很好的总结:
在某些环境下,有processor产生的临时对象是有必要的,也是比较方便的,这样的临时对象由编译器决定。
临时对象的销毁应该是对完整表达式求值过程的最后一个步骤。
因为临时对象是根据执行期语义有条件的产生,因此它的生命规则就显得很复杂。C++标准要求凡含有表达式执行结果的临时对象,应该保留到对象的初始化操作完成为止。当然这样也会有例外,当一个临时对象被一个引用绑定时,对象将残留,直到被初始化的引用的生命结束,或者超出临时对象的作用域。临时对象还有许多内容,但我觉得知道这么多就足够了,这还有不少是我看别人写的写上去的。总而言之,我们要知道这个东西什么时候冒出来,什么时候该死,才能更好的control

就到这里,写的手疼了 写完了 欢迎批评指正。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值