第六章 执行期语意学

第六章 执行期语意学

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

class X{
public:
  X();
  ~X();
  operator Y() const;
  X getValue();
};

执行代码if (yy == xx.getValue()) 会被编译器扩充为:

if (yy.operator==(xx.getValue().operator Y()))

其中还有各种临时变量的定义和析构:

{
    X temp1 = xx.getValue();
  	Y temp2 = temp1.operator Y();
    int temp3 = yy.operator==(temp2);
    if (temp3) ...
    temp2.Y::~Y();
    temp1.X::~X();
}

对象数组

Point knots[10] 对于一个对象数组,编译器会生成一个vec_new 函数和一个vec_delete 函数来负责对象的构造和析构。

void* 
vec_new(
	void *array;	//数组的起始地址
    size_t elem_size;	//每一个class object的大小
    int elem_count;		//数组中的元素个数
    void (*constructor)(void*),
    void (*destructor)(void*)
);
Point knots[10];
vec_new(&knots, sizeof(Point), 10, &Point::Point, 0);
void*
vec_delete(
	void *array;
    size_t elem_size;
    int elem_count;
    void (*destructor)(void*, char);
)

如果是Point knots[10] = {Point(), Point(1.0, 1.0, 0.5), -1.0}, 这种编译器会先对前三个元素进行直接初始化操作,对于后面7个元素调用vec_new 函数。

newdelete 运算符

使用new 运算符给对象分配空间:

Point3d *origin = new Point3d;
//转化为
Point3d* origin;
if (origin = _new(sizeof(Point3d)))
    origin = Point3d::Point3d(origin);
//带有异常处理
if (origin = _new(sizeof(Point3d))) {
    try {
        origin = Point3d::Point3d(origin);
    }
    catch(...) {
        _delete(origin);
        throw;
    }
}

new 运算符的底层实现:

extern void* operator new(size_t size) {
    if (size == 0) size = 1;
    void *last_alloc;
    while(!(last_alloc = malloc(size))) {
        if (_new_handler)
            (*_new_handler)();
        else return 0;
    }
    return last_alloc;
}

因为编译器会自动分配1大小的空间,所以new T[10] 是合法的。

针对数组的new 语意

如果一个对象没有定义默认构造函数和析构函数,针对数组的new调用的还是_new 运算符,而不是vec_new

int *p_array = new int[5];
//会被转化为
int *p_array = (int*)_new(5 * sizeof(int));
struct simple_arr { float f1, f2;}
simple_arr *p_aggr = new simple_aggr[5];

如果一个类含有默认构造函数和析构函数,执行的是vec_new

Point3d *p_array = new Point3d[10];
//被合成为
Point3d *p_array;
p_array = vec_new(0, sizeof(Point3d), 10, &Point3d::Point3d, &Point3d::~Point3d);

当我们析构一个对象数组的时候使用delete[] 运算符,采用delete[10] 在里面注明数字也是可以的,但是目前都不会显式的指定。如果对一个对象数组调用delete ,只会析构数组中的第一个元素。那么delete[] 又是如何知道元素个数的,vec_new 会在分配的内存块中配置一个额外的word 用来记录元素个数。

class Point {
public:
  Point();
  virtual ~Point();
};

class Point3d : public Point {
public:
  Point3d();
  virtual ~Point3d();
};

int main(int argc, char const *argv[])
{
  Point *ptr = new Point3d[10];
  delete [] ptr;
  return 0;
}

基类指针指向来分配子类对象的数组,最后再析构会发生如下的错误:

/usr/bin/ld: /tmp/ccDybcp6.o: in function main': case1.cpp:(.text+0x42): undefined reference to Point3d::Point3d()'
collect2: error: ld returned 1 exit status

当执行析构的时候,通过vec_delete进行析构,我们传过去的是Point 的析构函数,去析构Point3d的对象,大小都不一样,无法析构。

避免上述情况的出现最好的办法就是避免基类指针指向一个派生类对象组成的数组。如果非要这样写,只能通过显式的方式进行析构。

for (int ix = 0; ix < elem_count; ++ ix) {
    Point3d *p = &((Point3d*)ptr)[ix];
    delete p;
}

Placement Operator new的语意

重载的new 运算符,调用方式

Point2w *ptw = new (arena) Point2w;

其中arena指向内存中的一个区块,用来方式新产生的Point2w object , 具体的实现如下:

void* operator new (size_t, void* p) { return p; }

编译器再运行的时候会将其扩充为:

Point2w *ptw = (Point2w*)arena;
if (ptw != 0) ptw->Point2w::Point2w();

如果我们相对arena这块区域在进行重新利用不能直接使用delete 因为这样会将对象的内存区域也释放掉。

delete p2w;	//error, 内存区域被释放掉
p2w = new (arena) Point2w;

为了防止内存区域被释放掉,直接显式的调用析构函数:

p2w->~Point2w;
p2w = new (arena) Point2w;

posted on 2022-12-13 21:46  翔鸽  阅读(19)  评论(0编辑  收藏  举报