一个对vptr和析构函数的试验(请高手分析其原理)

huche 2004-06-15 10:24:43
class CStr
{
public:
cstr(){cout << "cstr function" << endl;}
virtual ~cstr(){cout << "~cstr function" << endl;}
virtual test(){cout << "test function" << endl;}
private:
char *str;
};

typedef struct
{
CStr str;
int jjj;
}tagTest;

int main(int argc, char* argv[])
{
tagTest *tag = new tagTest;
memset(tag, 0x00, sizeof(tagTest)); // tag->str->vptr被初始化为0x00
tag->str.test(); // 由于CStr::test()是虚函数,而vptr已被初始化为0x00,
          // 所以访问内存错误
delete tag; // 但是,如果上一行注释掉,同样是虚函数的析构函数能够被调用
return 0;
}

我的问题是如果析构函数不是通过vptr访问的话,为什么基类指向派生类的指针被delete的时候,能够先调用派生类的析构函数,再调用基类的析构函数呢?

请高手分析vptr和析构函数之间的关系!谢谢
...全文
254 21 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
MyNameEPC 2004-06-19
  • 打赏
  • 举报
回复
我晕!还发短消息给我告诉我错了!呵呵!!我要再次总结思想,哈哈!!在再次总结如下:
即:在楼主的代码里根本没有用到多态,所以根本没有用到vptr指针,所以程序运行正常。

再看代码,代码如下。其中有2个main()函数,一个用到了多态,另一个没有:
class B
{
public:
B(){cout<<"B()"<<endl;}
virtual ~B(){cout<<"~B()"<<endl;}
};

void main() // 没有用到多态,所以程序执行正常
{
B b;
memset(&b, 0, sizeof(B));
}

void main() // 有多态,所以出现异常
{
B *p = new B();
memset(p, 0, sizeof(B));
delete p;
}
MyNameEPC 2004-06-18
  • 打赏
  • 举报
回复
看看反汇编就知道了,我的代码如下(类名字等等改了)
class A
{
public:
A(){ cout<<"A()"<<endl; }
virtual ~A(){ cout<< "~A()"<<endl; }
virtual f(){ cout<<"f()"<<endl; }
private:
char *pc;
};

struct S
{
A a;
int n;
};

int _tmain(int argc, _TCHAR* argv[])
{
{ // 括号里的反汇编代码在下面
S *ps = new S();
memset(ps, 0, sizeof(S));
delete ps;
}
return 0;
}

反汇编:
{
S *ps = new S();
00416E3D push 0Ch
00416E3F call operator new (415D02h)
00416E44 add esp,4
00416E47 mov dword ptr [ebp-0F8h],eax
00416E4D mov dword ptr [ebp-4],0
00416E54 cmp dword ptr [ebp-0F8h],0
00416E5B je main+70h (416E70h)
00416E5D mov ecx,dword ptr [ebp-0F8h]
00416E63 call S::S (415B9Ah)
00416E68 mov dword ptr [ebp-10Ch],eax
00416E6E jmp main+7Ah (416E7Ah)
00416E70 mov dword ptr [ebp-10Ch],0
00416E7A mov eax,dword ptr [ebp-10Ch]
00416E80 mov dword ptr [ebp-104h],eax
00416E86 mov dword ptr [ebp-4],0FFFFFFFFh
00416E8D mov ecx,dword ptr [ebp-104h]
00416E93 mov dword ptr [ps],ecx
memset(ps, 0, sizeof(S));
00416E96 push 0Ch
00416E98 push 0
00416E9A mov eax,dword ptr [ps]
00416E9D push eax
00416E9E call @ILT+1145(_memset) (41547Eh)
00416EA3 add esp,0Ch
delete ps;
00416EA6 mov eax,dword ptr [ps]
00416EA9 mov dword ptr [ebp-0E0h],eax
00416EAF mov ecx,dword ptr [ebp-0E0h]
00416EB5 mov dword ptr [ebp-0ECh],ecx
00416EBB cmp dword ptr [ebp-0ECh],0
00416EC2 je main+0D9h (416ED9h)
00416EC4 push 1
00416EC6 mov ecx,dword ptr [ebp-0ECh]
00416ECC call S::`scalar deleting destructor' (4150D2h) // 优化了!!
00416ED1 mov dword ptr [ebp-10Ch],eax
00416ED7 jmp main+0E3h (416EE3h)
00416ED9 mov dword ptr [ebp-10Ch],0
}
huche 2004-06-17
  • 打赏
  • 举报
回复
我终于想明白了,是对对象这指针不同的处理造成的,对象调用析构在编译器时就已经编译好了,因为它不存在多态,对象指针则是在通过vptr访问的。

不过还有一点纳闷的就是通过对象调用虚函数为什么不也在编译的时候就编译好呢?
huche 2004-06-17
  • 打赏
  • 举报
回复
To MyNameEPC(MyName)

你总结错了,我也跟着你错了一下,我在VC.net和C++ builder编译器下测试,能够执行析构函数!并且跟踪到delete行是vptr显示为0x00000000!!!

还是要自己亲手认真测试啊!

 刚才我之所以会同意你的看法,因为我刚才是这样测试的:
  CStr str = new CStr();
memset(str, 0x00, sizeof( CStr));
delete str; // 内存访问错误

这和前面的有什么差别呢??还请高手解答!!
huche 2004-06-17
  • 打赏
  • 举报
回复
To MyNameEPC(MyName)
 我是在VC++ 6.0下测试的。
我在C++ Builder和VC++.NET下试了,的确都没有执行析构函数。
 
 那VC++6.0 是如何实现的呢?你有没有研究过?
dot99 2004-06-17
  • 打赏
  • 举报
回复
vptr只是C++标准"建议"编译器支持的一个标准而已, 而并不是"要求"编译器一定要这样做~
moon615 2004-06-16
  • 打赏
  • 举报
回复
delete tag;这行没有通过虚拟函数表调用析构函数,也就是说析构函数不是在运行阶段而是在编译阶段通过优化直接备妥。你如果调用tag->str.~cstr();也会出异常。
sandrowjw 2004-06-16
  • 打赏
  • 举报
回复
virtual的析构函数可以被编译器优化为普通的函数,从而跳过虚函数表查找的这个过程。
pengzhenwanli 2004-06-16
  • 打赏
  • 举报
回复
根据深度探索C++对象模型中的说法。
如果基类声明了虚函数,编译器将为基类初始化一个虚函数表。如果没有声明虚函数,则不会生成初始化虚函数表。
然后派生类替换相应的虚函数入口。达到虚函数调用的目的。
MyNameEPC 2004-06-16
  • 打赏
  • 举报
回复
大家不要讨论了,还是我来做总结吧!
先最好说说您用的是什么编译器,什么操作系统。因为我在Windows 2000 Server下调试了一下,使用的编译器分别是C++ Builder X、gcc、VC++.NET 2003,都没有执行析构函数,都出错了!
所以最后我认为是编译器和运行环境的不同,毕竟C++只有一个协议标准,没有一个实现标准。
sth4nth 2004-06-16
  • 打赏
  • 举报
回复
析构函数是按继承层次,从底层一次向上调。如果没在vptr中找到entry,就按名字找呗。只不过可能实际调用的不是派生类的析构函数,而是基类的。我猜的。
vcchunhong 2004-06-16
  • 打赏
  • 举报
回复
virtual test(){cout << "test function" << endl;}

病句``````
返回类型哪`
void`````
huche 2004-06-16
  • 打赏
  • 举报
回复
To moon615(王有财) 等人:
  碰到这个问题,我也是认为析构函数的调用在编译的时候就已经编译好了。所以我后面又问了一句,基类指向派生类的析构函数的调用难道也能在编译的时候就编译好了吗?
  所以我觉得问题并不这么简单,所以到这里请大家帮忙分析分析析构函数到底是怎么被调用的。

  那个结构和类是没有继承关系,只是一个组合关系而已,因为这段代码我还做了一个其它试验,所以就把代码直接拷贝上来了。
oldjackyone 2004-06-16
  • 打赏
  • 举报
回复
对不起,一时糊涂,王有才的说法是正确的....但我的那段解释肯定是正确的.
zf0579 2004-06-16
  • 打赏
  • 举报
回复
首先你这不是继承关系

其次答案是没有关系,
如果清空vtbl,调用tag->str.~cstr()同样会出错,
这种方式给程序员在某些情况下自杀一个对象用的。

而程序在析构对象时,通常都是直接cstr::~cstr的。

同意moon615(王有财) 的解释
yjh1982 2004-06-16
  • 打赏
  • 举报
回复
析构函数不是通过vptr访问的话
...........
虚析构函数是通过vptr访问
oldjackyone 2004-06-16
  • 打赏
  • 举报
回复
楼主的delete tag会直接调用成员的析构函数,而不通过vptr->vtable里查找~Cstr();地址,再调用函数。
oldjackyone 2004-06-16
  • 打赏
  • 举报
回复


To:王有财

tag->str.~cstr();在编译期间就会出现编译错误,好像应该改成(tag->str).~cstr();的吧,即使这样,也不会引起编译时刻错误.

现在楼主的问题主要出在根本就没有用到多态,没有做到迟后联编。

tagTest *tag = new tagTest;//这句就表明了不会使用到多态(tag不是基类指针),在编译期间就可以确定的东西,tag指针也不会指向vptr指针,因为根本不会出现迟后联编(虽然vptr在类对象中存在),所以根本不会有多态性的存在,你可以这样:


class C
{
public:
virtual ccCStr(){cout << "~CStr function1" << endl;}
virtual c1CStr(){cout << "~CStr function2" << endl;}
virtual c2CStr(){cout << "~CStr function3" << endl;}
virtual c3CStr(){cout << "~CStr function4" << endl;}
virtual c4CStr(){cout << "~CStr function5" << endl;}
};
class CStr : public C
{
public:
// CStr(){cout << "cstr function" << endl;}
virtual test(){cout << "test function" << endl;}
virtual ccCStr(){cout << "~CStr function1" << endl;}
virtual c1CStr(){cout << "~CStr function2" << endl;}
virtual c2CStr(){cout << "~CStr function3" << endl;}
virtual c3CStr(){cout << "~CStr function4" << endl;}
virtual c4CStr(){cout << "~CStr function5" << endl;}
virtual ~CStr(){cout << "~CStr function6" << endl;}

private:
char *str;
};


int main()
{
C *tag = new CStr;
memset( tag, 0x00, 4);
delete tag;
system("pause");
return 0;
}

必须有继承,才会有多态性的出现.

可以用断点调试,可以清楚的看到vptr的值是0x00.
sharkhuang 2004-06-16
  • 打赏
  • 举报
回复
编译器更具c++标准做的吧
merlinran 2004-06-16
  • 打赏
  • 举报
回复
按道理两次调用都不会出错的。因为在编译时就知道具体类型,也就不需要透过虚表来调用成员函数。
要测试,就要确保一定使用虚函数机制。给个例子:
class CStr2 : public CStr
{
public:
cstr(){cout << "Ctor of CStr2" << endl;}
virtual ~cstr(){cout << "Dtor of CStr2" << endl;}
virtual test(){cout << "CStr2::test" << endl;}
private:
char *str;
};

CStr* pobj = new CStr2;
pobj->test();
delete pobj;
然后再加上memset,我想delete也会出错的。
加载更多回复(1)

65,186

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧