现在的位置: 首页 > 综合 > 正文

C++几种析构

2018年03月16日 ⁄ 综合 ⁄ 共 2876字 ⁄ 字号 评论关闭

 

 

首先说下我对完全释放的理解。
完全释放是指经过了这系列的操作, 没有内存泄露。

三,四能够完全释放内存, 一,二由于条件不足, 不能确定。
但是四个使用的方式都是错误的, 这四种情况都不会导致运行错误, 可以算是一种巧合。

一点背景知识:
//////////////////////////////////////////////////
Class A;
A *pa = new A;
首先调用operator new(size_t) 来分配A所占据的内存空间, 然后在这个空间上调用一个函数,记做函数FA(注意不是A::A()):
{
  构造基类 // 如果有 
  构造成员对象变量 // 如果有
  调用自己的构造函数 // 如果有 A::A()
}
这是一个递归的过程, 既基类和成员变量的构造也有自己的FX函数。

如果基类和成员变量的FX都不存在,并且A::A()没写, 那么FA则不存在

最后吧这个空间给变量pa

Class A;
Class B : public A;
A *pa = new B;
delete pa;
这里分两种情况, 如果有virtual destructor和没有virtual destructor的情况
A或者A的任何层次继承的基类具有virtual destructor, 那么A则具有virtual destructor,否则则不具有。
有virtual destructor:
直接调用这个虚函数记做~FA (注意不是A::~A()), 因为虚函数的多态特性, 保证了
“对一个具有虚析构函数的基类指针调用delete, 能完全释放内存, 即便这个指针可能是派生类所产生的”
这是设计一个基类必须需要做的一件事, 比如你需要让别人继承你的话, 最起码要写一个空的虚析构函数。
c++的这个特性的实现一般采用这个过程。 这个在B的虚表中的~FA一般是这样的.
~FA thiscall (A *this, int flag)
{
  // 注意这里传递过来的this是A级别的, 因为这个函数的产生是因为A导致的!!
  调用B的析构函数B::~B() // 如果存在的话, 这里需要有一个修正this的过程, 即编译器根据A和B的层次
  // 关系来修正, 对于单重派生来说, 是一样的。
  析构B中的其他成员类对象
  析构基类
  if (flag & 1)
  operator delete (void*) // 释放内存
}
注: 这也是个递归的过程, 其中 "析构B中的其他成员类对象" 和 "析构基类" 分别有自己的~FX函数
关于这个flag是用来区别delete pa 和 pa->~A() 的.

只要是destructor是virtual的, 这个~FA必然存在, 因为他需要占据虚表项.

没有virtual的destructor
编译器会生成一个函数记做~FA2, 看起来像这个样子
~FA2 thiscall (A *this)
{
  调用A的析构函数A::~A();
  析构A的其他成员类对象
  析构A的基类
 }
这也是个递归的过程, 同样, 还存在一个函数, 记做~FB2, 看起来是这个样子
~FB2 thiscall (B *this)
{
  调用B的析构函数B::~B();
  析构B的其他成员类对象
  析构B的基类 // 即函数 ~FA2
}

执行delete pa的时候, 编译器
调用~FA2(), 然后在吧A层次的空间去调用operator delete(void *)
~FA2的不存在的条件是,A::~A()没有写并且他所有的基类和成员类对象的~FX都不存在。
A的其他成员类对象的则根据是否是virtual destructor来按照这两种法则析构.
如果是法则1则flag传递的是0.

这里有2个问题:
1: 没有经过FB2, 所以B的析构函数B::~B()和B的其他成员类对象没可能释放。
2: A层次空间的地址肯能不是B层次空间的地址, 强制operator delete可能会出错, 
虽然大多数情况下是一样的

好, 接下来最重要的是 
Class A;
A *pa = new A[2];
delete []pa;
第一次分配了多少内存?
可能是sizeof(A)*2 也可能是sizeof(A)*2 + sizeof(size_t)
第一种情况吧operator []new(size_t)的地址给pa
第二种情况吧operator []new(size_t) + sizeof(size_t)的地址给pa

这完全看这个~FA或者~FA2是否存在!

为什么这么说, 看看delete[]pa吧,
[]告诉编译器这是一个数组对象的析构, 我需要吧析构的过程应用到
所有的对象上, 所以我必须知道对象的个数, 然后吧从这个地址开始的每个
对象都去析构一把, 这个个数则记录在pa这个地址-sizeof(size_t)的地方!

但是, 如果析构是无动作的, 则没有记录这个size的必要

一点小的细节就是A如果是virtual destructor则循环调用那个虚函数
否则编译器可能生成一个 vector_~FA2(A *this, int flag)来释放
if (flag & 2) 则是一个vector析构,需要循环, 否则是就是~FA2(A *this, int flag)

对照上述不难发现
一: 
class A; 
class B : public A 
//假设类B比类A要大,则: 

A* pA = new B; 
delete pA; 
B或者A有虚析构, 一切工作正常,
否则B的析构函数不被调用, B的成员类对象不被析构, 但是
operator delete的时候能够pa占据的地址释放, 因为单继承是同一地址

二:
class A; 

void* p = new A; 
delete p; 
因为是原生类型, 所以直接operator delete掉了, 地址是A的地址,能够正确释放
但是~FA或者~FA2都不会被调用, 可能会A锁包含的指针, 对象, 基类等占据的各种资源不被释放

三:
struct s 

int i; 
int j; 
}; 

LPBYTE pData = new BYTE[100]; 
struct s* ps = (struct s*) pData; 
delete ps; 
显然没加[]的是一个单对象析构, 但是~Fs2是不存在的, 所以不被调用,
只是operator delete, 地址也对上了.

四: 
class A 

void Release(){delete this;} 
}; 

class B : public A 

int i; 
}; 

B* pb = new B; 
pb->Release(); 
这个Release是因为A生成的, 所以这个delete this其实是删除A,
由于A和B的地址相同, 所以空间能释放, 也能正确的调用~FA2, 但是~FB2没机会被调用了
庆幸的是他们都不存在.

关于__stdcall的说明,作为一个微软特有的调用约定限制符, msdn有详细的说明
Microsoft Specific

The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype

抱歉!评论已关闭.