深度分析C++析构函数的作用
#include<iostream>usingnamespacestd;classBase{public:inti;virtualvoidf(){cout<<"Base:...
#include<iostream>
using namespace std;
class Base
{
public:
int i;
virtual void f(){cout<<"Base::f()"<<endl;}
~Base(){}
};
class Derived:public Base
{
public:
int j;
void f(){cout<<"Derived::f()"<<endl;}
~Derived(){};
};
int main()
{
Derived *p;
Derived d;
p=&d;
p->f();
d.Base::~Base();
p->f();
return 0;
}
输出:
Derived::f()
Base::f()
为什么调用了基类的析构函数,虚函数表就改变了?
请深度解析一下,编译器在析构函数里添加了哪些任务 展开
using namespace std;
class Base
{
public:
int i;
virtual void f(){cout<<"Base::f()"<<endl;}
~Base(){}
};
class Derived:public Base
{
public:
int j;
void f(){cout<<"Derived::f()"<<endl;}
~Derived(){};
};
int main()
{
Derived *p;
Derived d;
p=&d;
p->f();
d.Base::~Base();
p->f();
return 0;
}
输出:
Derived::f()
Base::f()
为什么调用了基类的析构函数,虚函数表就改变了?
请深度解析一下,编译器在析构函数里添加了哪些任务 展开
展开全部
这样的问题, 有一个最实用的方法, 就是去跟一下汇编的代码, 当然了这需要你懂一点点的汇编语言.
问题的原因就是: 当你调用基类的析构函数d.Base::~Base()时, C++会先把d对象中的虚函数表改变一下, 原来指向Derived类的虚函数表, 现在指向Base类的虚函数表. 汇编代码为:
004116E3 mov eax,dword ptr [this]
004116E6 mov dword ptr [eax],offset Base::`vftable'
这里就把Base的vtable给对象d了.
至于为什么C++要这么处理, 一楼的基本上说出来了, 就是在构造函数和析构函数里多态性是被冻结了的, 在这两个函数中没有多多态性.
具体说明如下:在构造或者析构函数中调用没有实现的纯虚函数是很恐怖的,但是一般这个动作编译器都能检查出来;但是如果中间隔了一层(构造函数调用非纯虚函数A,而A调用了纯虚函数),编译器就无能为力了,然后就等着运行时错误出现吧~~~
而且,在构造函数或析构函数的上下文中,虚函数是没有意义的,它只调用本类的那个函数。因为,就构造函数来说,基类的构造函数总是先调用,而根据前面所说的,基类的构造函数中会被编译器插入一些填写本类型虚函数表的代码,也就是在执行基类构造函数中的用户代码时,此时虚函数表中的保存的仅是本类型的虚函数地址。也就是在构造时,多态是被freeze的。对应析构函数则是相反的过程,原理是一样的
至于析构函数还执行其它什么任务了, 我帖出来这段的汇编代码你就知道了:
~Base()
{
00411D80 push ebp
00411D81 mov ebp,esp
00411D83 sub esp,0CCh
00411D89 push ebx
00411D8A push esi
00411D8B push edi
00411D8C push ecx
00411D8D lea edi,[ebp-0CCh]
00411D93 mov ecx,33h
00411D98 mov eax,0CCCCCCCCh
00411D9D rep stos dword ptr es:[edi]
00411D9F pop ecx
00411DA0 mov dword ptr [ebp-8],ecx
// 注意下面这两句仅对于含有虚函数的类才有
004116E3 mov eax,dword ptr [this]
004116E6 mov dword ptr [eax],offset Base::`vftable'
// 就是这里把虚函数指针改变了的
}
00411DB4 pop edi
00411DB5 pop esi
00411DB6 pop ebx
00411DB7 add esp,0CCh
00411DBD cmp ebp,esp
00411DBF call @ILT+460(__RTC_CheckEsp) (4111D1h)
00411DC4 mov esp,ebp
00411DC6 pop ebp
00411DC7 ret
说实话, 除了你碰到的这个问题, 还真是没有其它什么值得你注意的问题了.
不知道这么说, 你明白了没有. 有问题的话你可以再补充问题
问题的原因就是: 当你调用基类的析构函数d.Base::~Base()时, C++会先把d对象中的虚函数表改变一下, 原来指向Derived类的虚函数表, 现在指向Base类的虚函数表. 汇编代码为:
004116E3 mov eax,dword ptr [this]
004116E6 mov dword ptr [eax],offset Base::`vftable'
这里就把Base的vtable给对象d了.
至于为什么C++要这么处理, 一楼的基本上说出来了, 就是在构造函数和析构函数里多态性是被冻结了的, 在这两个函数中没有多多态性.
具体说明如下:在构造或者析构函数中调用没有实现的纯虚函数是很恐怖的,但是一般这个动作编译器都能检查出来;但是如果中间隔了一层(构造函数调用非纯虚函数A,而A调用了纯虚函数),编译器就无能为力了,然后就等着运行时错误出现吧~~~
而且,在构造函数或析构函数的上下文中,虚函数是没有意义的,它只调用本类的那个函数。因为,就构造函数来说,基类的构造函数总是先调用,而根据前面所说的,基类的构造函数中会被编译器插入一些填写本类型虚函数表的代码,也就是在执行基类构造函数中的用户代码时,此时虚函数表中的保存的仅是本类型的虚函数地址。也就是在构造时,多态是被freeze的。对应析构函数则是相反的过程,原理是一样的
至于析构函数还执行其它什么任务了, 我帖出来这段的汇编代码你就知道了:
~Base()
{
00411D80 push ebp
00411D81 mov ebp,esp
00411D83 sub esp,0CCh
00411D89 push ebx
00411D8A push esi
00411D8B push edi
00411D8C push ecx
00411D8D lea edi,[ebp-0CCh]
00411D93 mov ecx,33h
00411D98 mov eax,0CCCCCCCCh
00411D9D rep stos dword ptr es:[edi]
00411D9F pop ecx
00411DA0 mov dword ptr [ebp-8],ecx
// 注意下面这两句仅对于含有虚函数的类才有
004116E3 mov eax,dword ptr [this]
004116E6 mov dword ptr [eax],offset Base::`vftable'
// 就是这里把虚函数指针改变了的
}
00411DB4 pop edi
00411DB5 pop esi
00411DB6 pop ebx
00411DB7 add esp,0CCh
00411DBD cmp ebp,esp
00411DBF call @ILT+460(__RTC_CheckEsp) (4111D1h)
00411DC4 mov esp,ebp
00411DC6 pop ebp
00411DC7 ret
说实话, 除了你碰到的这个问题, 还真是没有其它什么值得你注意的问题了.
不知道这么说, 你明白了没有. 有问题的话你可以再补充问题
展开全部
哇,这什么学校,讲得好详细啊,这个都考。
析构函数开始之前,要把虚函数表改成本类的虚函数表。确保在析构函数中调用的virtual成员是本类的。因为这时子类的析构函数已经运行过了,子类那部分数据已不再可用。
析构函数开始执行到你的代码时,该对象就变成一个基类对象继续析构了。
你的代码之后,析构成员,析构基类子对象。
析构函数开始之前,要把虚函数表改成本类的虚函数表。确保在析构函数中调用的virtual成员是本类的。因为这时子类的析构函数已经运行过了,子类那部分数据已不再可用。
析构函数开始执行到你的代码时,该对象就变成一个基类对象继续析构了。
你的代码之后,析构成员,析构基类子对象。
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
构造函数 为类分配内存
析构函数 为类释放内存,包括虚函数表
析构函数 为类释放内存,包括虚函数表
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
展开全部
虚函数表不可能变化的,对于每一个实例来讲,他保存的仅仅只是一个指向虚函数表的指针(虚函数表每个类只有一份,和类的相关数据放在一块儿)。。所以你显式地调用父类析构函数后,虚函数表没有被破坏,被破坏的只是实例对象的那个虚函数表指针。我们可以做个试验,:
void showint(void *p, int size)
{
unsigned int *n = static_cast<unsigned int*>(p);
for(int i = 0; i < size>>2; i++)
cout<<n[i]<<endl;
cout<<endl;
}
int main()
{
Base b;
b.i = 123;
showint(&b, sizeof(b));
cout<<"=============="<<endl;
Derived *p;
Derived d;
d.i = 1;
d.j = 2;
p=&d;
p->f();
cout<<"=============="<<endl;
showint(p, sizeof(d));
d.~Derived();
cout<<"the Base's destructor func called."<<endl;
showint(p, sizeof(d));
cout<<"=============="<<endl;
p->f();
system("pause");
return 0;
}
注意我是在你代码基础上改的,加了个函数showint,main也做了些修改
运行结果:
14710868
123
==============
Derived::f()
==============
14711108
1
2
the Base's destructor func called.
14710868
1
2
==============
Base::f()
Press any key to continue . . .
****************************
可以看到,第一行我加的一个base对象的虚函数表在14710868处
而后来可以看到,一个子类Derived的虚函数表在14711108处
单调用了析构函数后,该指针却被指向了父类虚函数表14710868处
=========
事实上程序员一般不应该显式地调用析构函数的,你这样调用了照成了程序行为未定义,虚函数表指针被破坏也就不足为奇了。。
=====
楼主要还想知道为什么会被破坏,我个人倒没研究过到这一层,不过还是有路子的:1、看侯捷那本《深入理解C++对象模型》掌握c++对象模型的理论知识; 2、然后阅读编译器产生的汇编代码(GCC的话加-s开关,微软的编译器在工程选项里面有对应项)
void showint(void *p, int size)
{
unsigned int *n = static_cast<unsigned int*>(p);
for(int i = 0; i < size>>2; i++)
cout<<n[i]<<endl;
cout<<endl;
}
int main()
{
Base b;
b.i = 123;
showint(&b, sizeof(b));
cout<<"=============="<<endl;
Derived *p;
Derived d;
d.i = 1;
d.j = 2;
p=&d;
p->f();
cout<<"=============="<<endl;
showint(p, sizeof(d));
d.~Derived();
cout<<"the Base's destructor func called."<<endl;
showint(p, sizeof(d));
cout<<"=============="<<endl;
p->f();
system("pause");
return 0;
}
注意我是在你代码基础上改的,加了个函数showint,main也做了些修改
运行结果:
14710868
123
==============
Derived::f()
==============
14711108
1
2
the Base's destructor func called.
14710868
1
2
==============
Base::f()
Press any key to continue . . .
****************************
可以看到,第一行我加的一个base对象的虚函数表在14710868处
而后来可以看到,一个子类Derived的虚函数表在14711108处
单调用了析构函数后,该指针却被指向了父类虚函数表14710868处
=========
事实上程序员一般不应该显式地调用析构函数的,你这样调用了照成了程序行为未定义,虚函数表指针被破坏也就不足为奇了。。
=====
楼主要还想知道为什么会被破坏,我个人倒没研究过到这一层,不过还是有路子的:1、看侯捷那本《深入理解C++对象模型》掌握c++对象模型的理论知识; 2、然后阅读编译器产生的汇编代码(GCC的话加-s开关,微软的编译器在工程选项里面有对应项)
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询