执行期语意学

扫码查看

  一个简单的例子

class Y
{
public:
   bool operator==(const Y&) const;
};
class X
{
public:
   operator Y() const;
   X getValue();
};

X xx;
Y yy;
if(yy==xx.getValue())
//会发生下列转换
X temp1=xx.getValue();
Y temp2=temp1.operator Y();
int temp3=yy.operator==(temp2);
if(temp3)
//...

temp2.Y::~Y();
temp1.X::~X();

一、对象的构造和析构

  一般而言,我们将object尽可能放置在使用它的那个程序区段附近,这样可以节省非必要的对象的构造和析构成本。

  destructor必须被放在每一个离开点(当时object还活着)之前。

全局对象

  已经初始化全局对象均存储在data segment(数据段),未初始化的全局变量存储在BSS(block started by symbol),C++中如果全局对象没有显式初始化,那么该对象所配置到的内存内容为0,但是构造函数一直到程序启动才会实施。

  全局对象如果有constructor和destructor的话,那么他需要静态的初始化操作和释放内存操作。

  C语言中的一个全局对象只能够被一个常量表达式(可在编译时期求值的那种)设定初始值。

munch方法:全局变量静态初始化的方法

  1. 为每一个需要静态初始化的文件产生一个__sti()函数(sti: static initialization),内含必要的constructor调用操作或者inline expansions。
  2. 为每一个需要静态的内存释放操作产生__std()函数(std: static deallocation),内含必要的constructor调用操作或者inline expansions。
  3. 提供一组running library”munch”函数:调用所有的__sti()一个_main()函数以及一个调用__std()函数的exit()函数。

Matrix identity1, identity2;
main() {
    Matrix m1 = identity, m2 = identity2;
    ...
    return 0;
}

//对应much策略如下
main() {
    _main();
    //_main()调用__sti_identity1()以及__sti_identity2();对所有的global做static initialization操作
    ...
    exit();
    //exit()调用__std_identity1()以及__std_identity2();对所有的global做static deallocation操作
    return 0;
}

  使用静态初始化object会有一些缺点,比如如果支持exception handling,那些objects将不能被置于try区间之内,这对于静态被调用的constructor可能是无法接受的。

静态局部对象

const Matrix&
identity() {
    static Matrix mat_identity;
    return mat_identity;
}
  1. mat_identity的构造函数只执行一次,即使identity函数调用多次。
  2. mat_identity的析构函数也只执行一次,即使identity函数调用多次。

  编译器的策略是无条件在程序起始处构造对象,这会导致所有的静态对象在程序起始时都初始化,即使调用它们的函数从来没有被调用过。

  解决办法是:取出local object地址(由于object是static,其地址在downstream component中将会被转到程序用来放置global object的data segment中)

  新的规则要求编译单位中的局部静态对象必须被摧毁,以构造相反的顺序摧毁。由于这些object都是第一次需要时才被构造(如每个含有static local class objects的函数第一次被进入时)所以编译器无法预期集合以及顺序,为了支持新规则,可能需要被产生出来的static class object保持一个执行期链表。

对象数组

Point knots[10];

  假如Point定义了一个默认构造函数,在从cfront的策略中,产生一个vec_new函数(如果含有virtual base class,产生vec_vnew()函数)(有构造函数时才会生效),用来构造数组:

void vec_new(
    void* array,    //持有的若不是具名数组的地址,就是0,如果是0数组将由应用程序的new运算符被动态配置于heap中
    size_t elem_size,    //数组中每一个元素的大小
    int elem_count,     //数组中元素的个数
    void (*constructor)(void*),  //形参个数为0的默认构造函数指针
    void (*destructor)(void*, char) //析构函数指针
)

  上树的转化为

Point knots[10];
vec_new(&knots, sizeof(Point), 10, Point::Point, 0);

  同样地,cfront的策略中,产生一个vec_delete函数(或是一个vec_vdelete(),如果class含有virtual base class的话  ),用来析构数组

void vec_delete(
    void* array,    //数组的起始地址
    size_t elem_size,    //数组中每一个元素的大小
    int elem_count,     //数组中元素的个数
    void (*destructor)(void*, char) //析构函数指针
)

  如果程序提供一个或多个明显初始值给一个由class objects组成的数组,vec_new()不再有必要。

Default Constructor和数组

  如果一个类的构造函数有一个或一个以上的默认参数值,例如:

class complex{
    complex(double = 0.0, double = 0.0);
}

  那么当我们写下complex array[10];时,编译器最终需要调用

vec_new(&array, sizeof(complex), 10, &complex::complex, 0);

  这里的&complex::complex需要是无参的构造函数,那么应该怎么做?做法是:在&complex::complex中调用我们自己提供的的constructor,并将default参数值显式指定过去,例如:

complex::complex() {
    complex(0.0, 0.0);  //调用我们自己的构造函数
}

二、new和delete运算符

12-21 01:02
查看更多