深入探索C++对象模型之六 --- 执行期语意学

深入探索C++对象模型之六 — 执行期语意学

C++最困难的一点就在于:无法从程序源码看出程序表达式的复杂度。因为编译器会在背后给你做很多的工作。对于一些表达式诸如T a = b +c,编译器可能会创建在执行期过程创建临时对象,那么在程序的出口处编译器就需要安插必要的代码来保证创建的临时对象都得到有效的析构。如果遇到goto、switch等会产生多个逻辑出口的,那么每个出口处都要安插代码。因此一般而言,我们会把object的定义尽可能放置在使用它的那个程序区段附近,减少不必要的对象产生和销毁操作。

其实上面的一点反映在C++中就是我们可以在函数内部的任意地方定义变量,但是C程序员却习惯而且也只能在函数起始处定义所有变量。

全局对象

c++程序中所有的global objects都是被放置在程序的data segment中。在编译时期class object内容为0,但是constructor需要到程序激活时才会触发。因此必须对一个object做静态初始化。

另外考虑到跨平台移植性的问题,出现了一个munch策略。

  1. 为每一个需要静态初始化的档案都产生一个_sti()函数,内带必要的constructor调用或者inline expansions。
  2. 为每一个需要静态内存释放的操作产生_std()函数,内带必要的destructor调用操作或者inline expansions。
  3. 在main函数中产生一个_main()和一个_exit()函数,其中_main()用于执行之前产生的所有的_sti()函数,而_exit()用于执行之前产生的所有的_std()函数。

具体如下图所示:

上面提到的编译器是如何收集一个程序中所有的_sti()和_std()的呢?这里不介绍(每家厂商的编译器实现方法都不尽相同)

局部静态对象

对于局部静态对象,我们必须要保证它的constructor和destructor都只能施行一次!!!

new和delete运算符

针对如下代码

int *pi = new int(5)

事实上是分两个步骤完成的:

  1. 调用函数库的_new(int size)来分配所需要的内存
  2. 给配置而来的对象设置初值或者是调用class的constructor操作,这些都必须是在分配空间成功之后进行

上面说的是new运算符,而相对的delete运算符就会去做释放内存空间的操作

  1. 检测指针是否为0
  2. 如果指针不为0,则释放指针指向的内存空间

对于deletedelete [],如果是要释放数组的内存空间那么必须使用后者,如果是前者的话编译器会认为是释放第一个元素。很久以前在调用delete []的时候是需要代码设计者自己指明元素的个数的,现在编译器可以自己查找,即使程序员手动指明元素个数也会被编译器忽略。

编译器通过在数组的最前端增加一个额外的word,然后把元素书目存放在这个word里面,来达到delete的时候确定元素个数。

另外一个要注意的是,如果使用base class的指针指向derived class的数组的话,那么在调用delete []的话仍然会存在问题,因为delete会按照base class的大小来释放内存,因此我们只能迭代走过整个数组,通过把指针类型强制转换成derived class类型的。如下:

Point *ptr = new Point3d[10]

//如下方式会按照Point的大小释放内存,但是却是错误的
delete []ptr;

//必须如下做
for(int ix=0; ix < elem_count; ++ix) {
    Point3d *p = &((Point3d*)ptr)[ix];
    delete p;
}
Placement Operate new的语意

有一个预先定义好的重载的new运算符,成为placement operator new,它需要第二个参数,类型是void *。

Point2w *ptw = new(arena) Point2w;

其中arena指向内存中一段空间

Placement operator new只是用来分配所要求的空间,但是不调用相关对象的构造函数。因此placement operator new可以用来决定objects被放置在哪里。

考虑如下代码:

void foobar() {
    Point2w *p2w = new(arena) Point2w;
    //do it...
    p2w = new(arena) Point2w;
}

上面的代码在一个已经存在的object上构造新的object,那么即使该class object有一个destructor,该destructor也不会被调用的。

临时性对象

在对完整表达式求值过程中,可能会产生临时性对象,那么摧毁临时性对象,就必要时在完整表达式的最后一个步骤中,这就保证了该内存区域中的值已经不在被使用的时候。否则会造成该临时性对象已经被摧毁释放掉,但是该内容还会被访问到,这会导致未定义的错误。

如果一个临时性对象被绑定于一个reference,对象将残留,直到被初始化之refenece的生命结束,或者直到临时对象的生命范畴结束。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值