2012-8-22
一般情况下(特指基类不使用novtable属性),debug编译出来的exe,派生类对象的析构,在析构基类部分的时候,对象的虚函数表指针值项会被修改成基类的虚函数表地址,然后再执行基类的析构函数体。
其中高亮的那一行是修改对象的虚函数表指针值为基类虚函数表地址
release下编译出来的就不是这样子。以一个小测试程序为例,编译器做了优化,并没有在调用基类析构函数时做赋值。但不确定编译器是否会一直做优化,因为看到了派生类析构函数体实质内容执行之前对虚函数表指针值做了一次赋值,那么为什么在基类析构函数体实质内容执行之前就不需要赋值?编译器优化的事情就不说了,吃力不讨好。下边图示调用派生类析构函数时做的事情,包括派生类部分和基类部分。
标红的地址是派生类析构函数执行前期对虚函数表指针值赋值
注1:novtable我是从这篇文章里看到的:
http://www.cnblogs.com/chio/archive/2007/09/09/887598.html
注2:我使用的工具为VS2005、OllyDbg。
2.不要在构造函数、析构函数里调用虚函数
根据网络上一些文章的讲解,可能出现“Pure Virtual Function Called”的错误情况有5种:
1) 基类构造器直接调用虚函数;
2)基类析构器直接调用虚函数;
3)基类构造器间接调用虚函数;
4) 基类析构器间接调用虚函数;
5)通过悬垂指针(dangling pointer,又称野指针)调用虚函数。
“dangling pointer”导致的“Pure Virtual Function Called”,在vs2005上测试了并没有出现,因为delete之后,对象的内存值会被修改为某些标志值。可能在其它编译器上会出现。
一些书籍:
《Effective C++第三版》 建议不要在构造、析构时调用虚函数。
《深度探索C++对象模型》在讲解vptr部分时,也分析过在基类构造函数里调用虚函数会出现的情况,它说,这些调用都是“静态决议”的,无论直接或间接调用虚函数。但实际上可能不是这样子,这取决于编译器的实现。VS2005就不是,直接调用会静态绑定,而间接调用是动态绑定。
如果是小程序的话,这种错误很容易发现,如果工程大了,这样的错误非常难找到。所以,书里的建议必须重视。优秀的程序员不是懂得“回”字有多少种写法,而是有很多写好工程的好习惯。
3. 网络上一些不错的总结:
http://www.artima.com/cppsource/pure_virtual.html (非常完整)
http://www.codeproject.com/Articles/14879/Pure-Virtual-Function-Call (很简单容易看)
http://blog.csdn.net/kikikind/article/details/2645316 (中文的总结汇总,很简单容易看懂)
http://www.cnblogs.com/chio/archive/2007/09/09/887598.html (分析汇编,底层而完善)
一些短小的验证代码:
https://files.cnblogs.com/cswuyg/%E7%94%B1Pure_Virtual_Function_Called%E8%80%83%E8%99%91%E5%88%B0%E7%9A%84.rar