Bug:C++运行时调用纯虚函数

昨天服务器宕机,打印出的日志非常诡异,宕在纯虚函数调用处。
    日志显示,战斗对象的虚函数调用,前几次正常,某个时刻过后“丧失多态”了,直接调到父类虚函数处,引发纯虚函数宕机。
    且win平台下运行正常,上linux必跪,老项目linux工具不全,debug版本都编不出来,只有Log;windows下还复现不出来。

找这个bug的过程还是蛮有意思的。记录下(*^__^*)

以往没碰到过这种Bug,起初当然毫无头绪。
    首先想到,c++中经常的内存改写,但能正常调用到普通虚函数,应该不是memset这样的东西把对象写坏。
    进一步分析,要能正常调到父类虚函数,那对象虚指针一定指向了正确的父类虚表。

回忆c++构造函数流程:
        1、假设CBase里有几个纯虚函数 CObj 继承它
        2 、CObj 的构造顺序:先构造 CBase 的部分,此时对象首地址的虚指针指向了 CBase的虚表……再接着构造 CObj 新增的部分,改写对象首地址的虚指针,指向ClassObj的虚表

如果析构函数按对应顺序反过来,容器里保存的 CBase* 指针,经过析构后,指向的对象,它的首地址就被改写为指向 CBase 虚表了。

这样就会出现日志看到的情况。

但我不确定析构函数是不是会改虚指针,按照构造、析构对称的思路预计是会的。
    网上也没查到资料,决定写代码实验~~结果不会

…………没啥线索了

晚上想起编译器对拷贝构造函数的优化,默认生成的拷贝构造函数其实不会被调用(没有副作用),直接优化为字节拷贝即可。
    写的测试代码里没显式声明析构函数,会不会也被编译器跳过了。所以 delete 后,首地址的vptr还是没变。

今天来立马改了测试代码,在父类里加上析构函数声明、实现……果然,析构后对象首地址的内容被改写了
    Obj* pB = new Obj();

printf("addr(%d) \n", *((int*)pB));

delete pB;

printf("addr(%d) \n", *((int*)pB));

至此,可以肯定服务器宕机,就是因为战斗对象被析构,虚指针被改写为指向父类虚表,业务层再拿来用时就跪了。
    (因为用到内存池,所以没出现悬垂指针的问题)

剩下的就好查了,delete对象时某业务模块仍持有其指针,没清理。搜搜战斗对象的引用关系,几分钟便找到问题所在。

战斗城池里有个守卫列表,npc进入时会把自己指针放入这个列表,死亡时没去清。

别人再来打这个城池时,跑战斗流程就调了纯虚函数,宕机。

尾声:
    觉得这个bug挺有深度的,能扣的地方很多。
    比如,为什么在win下不会宕机呢?项目里的战斗对象也是没显式析构函数的,应该是被vs编译器优化掉了,而Linux没有。
    再比如,如果没有内存池,那两边应该都会出现悬垂指针,直接宕机……提前暴露问题所在,反而更好分析定位Bug。
    还有,win环境下,即便免去了纯虚函数的宕机问题,但确将Bug隐藏的更深了。后面业务逻辑再从内存池取指针,拿到那个旧的,胡乱一改,再出问题时候,你看到的就是一坨shit了,鬼知道到底是哪改坏的 ( ̄﹁ ̄)~

还是我们老大说的好:
        内存池如果是新项目,我估计不会使用,会直接用TCMALLOC之类的。我还是想能工程化就工程化,C++开发还是要往库的思维走。不然老挖坑填坑。

PS: 没头绪下班前,我干了三件事情:
                在前C++项目群里描述问题,询问“有谁碰到过中途调纯虚函数,服务器宕机的情况”;
                在加入的技术群里问;
                在知乎提问,邀请轮子哥、R大
        次天来就看到有人回复:子类析构掉的话,虚表会被改写成iobj的虚表,析构过的指针,可以调iobj的虚函数,调其它虚函数则会挂
        即使自己没能想到“析构过程可能被编译器优化掉”,也能在他们的指导之下找到问题的。
        利用别人的经验哈 b( ̄▽ ̄)d

时间: 2024-12-21 04:44:14

Bug:C++运行时调用纯虚函数的相关文章

Hello,C++(5)纯虚函数和抽象类

纯虚函数和抽象类 下面通过一个例子来说明纯虚函数的定义方法 在这个类当中,我们定义了一个普通的虚函数,并且也定义了一个纯虚函数.那么,纯虚函数是什么呢??从上面的定义可以看到,纯虚函数就是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”. 纯虚函数的实现原理 本节从虚函数表的角度来说明纯虚函数的实现原理. 上面就是我们在前面课程讲到的多态的实现原理,在讲这一部分的时候,讲到了虚函数表以及虚函数表指针.如果我们定义了Shape这样的类,那么,Shape类当中,因为有虚函数和纯虚函数,所以

纯虚函数和抽象类

-------------------siwuxie095 纯虚函数 在 C++ 中,用 纯 字来修饰虚函数,即 纯虚函数 纯虚函数没有函数体,同时在定义时,函数名的后边要加 =0 看如下实例: 纯虚函数在虚函数表中的实现: 如果定义了一个 Shape 类,因为其中有虚函数和纯虚函数, 所以 Shape 类一定有一个虚函数表,当然,也就会有一个 虚函数表指针 在虚函数表中,如果是一个普通虚函数,则对应的函数指针 就是一个有意义的值,如果是一个纯虚函数,则对应的函数 指针的值就是 0 抽象类 纯虚

15.6纯虚函数与抽象类

纯虚函数:virtual 1.问题的产生: 通过基类的指针删除派生类对象时只调用基类的析构函数 例: class CSon{ public: ~CSon() {   }; }; class CGrandson : CSon{ public: ~CGrandson() {   }; } int main(){ CSon *p = new CGrandson;       delete p;       return 0; } 2. 解决办法: 把基类的析构函数声明为virtual,派生类的析构函数

C++中纯虚函数

1.纯虚函数 virtual ReturnType Function()= 0; 纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义.凡是含有纯虚函数的类叫做抽象类.这种类不能声明对象,只是作为基类为派生类服务.除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象. 一般而言纯虚函数的函数体是缺省的,但是也可以给出纯虚函数的函数体(此时纯虚函数变为虚函数),这一点经常被人们忽视,调用纯虚函数的方法为baseclass::vi

虚析构函数与纯虚函数

虚析构函数 通过基类的指针删除派生类对象时,通常情况只调用基类的析构函数 但是,删除一个派生类对象时,应该先调用派生类的析构函数,然后调用基类的析构函数(构造时自顶向下,析构时自底向上) 这种情况会产生内存泄漏,最终导致系统应可用内存不足而崩溃 解决办法 把基类的析构函数声明为virtual,此时派生类的析构函数即使不声明为virtual也为virtual函数 在调用基类的指针删除派生类对象时,会先调用派生类的析构函数,最后调用基类的析构函数 一般方法 如果一个类定义了虚函数,那么析构函数也应该

关于纯虚函数

在定义和实现COM接口时,纯虚函数有不可替代的作用.举个例子,可能有些牵强,但是能说明问题.比如,我有一个类Animal,维护了一种动物形象,现在我要实现一个函数Draw,把这个动物画到任意一种介质上,这种介质可能是屏幕也可能是位图,当然,在屏幕和位图上画图形肯定是不一样的,那么,我的Draw函数肯定要有一个介质参数,每种介质对应一个版本的Draw函数,这样很麻烦,有了纯虚函数,就完全可以定义一个抽象类,以这个抽象类为参数,只写一个版本的Draw函数就可以了.这个抽象类就是把在屏幕和位图上的绘图

纯虚函数也可以有内容,并被子类调用

#include <iostream.h> class base { public: virtual void v() const = 0; virtual void f() const = 0 { // 纯虚函数也有实现内容,但子类仍然必须实现这个虚函数 cout << "base::f()\n"; } }; void base::v() const { cout << "base::v()\n";} // 实现函数写在类的外部

C++:抽象基类和纯虚函数的理解

转载地址:http://blog.csdn.net/acs713/article/details/7352440 抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层. ⑴抽象类的定义: 称带有纯虚函数的类为抽象类. ⑵抽象类的作用: 抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作.所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些

C++纯虚函数、虚函数、实函数、抽象类,重载、重写、重定义

首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略.纯虚函数则是一种特殊的虚函数.虚函数联系到多态,多态联系到继承. 一.虚函