项目场景:
代码集成三方库后,在程序即将退出时,报错崩溃,程序没有正常退出。提示如下错误: pure virtual method called terminate called without an active exception使用gdb调试,查看程序堆栈
问题描述:
问题并不是必现,大多数情况下都能正常执行完,且异常基本都出现在程序即将退出时。崩溃时打印pure virtual method called
字面翻译是:纯虚函数被调用。
原因分析:
奔溃时的打印信息是定位问题的入口。 首先确定pure virtual method called这句话是哪里打印的,虚函数是C++的内容 ,且从gdb的奔溃信息看,是libstdc++.so库中的内容,将这段打印信息在gcc中查找 在libstdc++-v3/libsupc++/pure.cc +49
继续查找__cxa_pure_virtual函数,在gcc/cp/decl.c +4405中
最终在gcc/cp/class.c +9277,看到这样的描述,程序在执行时会给纯虚函数赋值,这个值就是__cxa_pure_virtual。
当你构造一个派生类的实例时,具体发生了什么?如果类包含虚函数表,过程会像下面这样:
第一步:构造最顶层的基类部分
a、让实例指向基类的虚函数表
b、构造基类实例成员变量
c、执行基类构造函数
第二步:构造派生部分(递归的)
a、让实例指向派生类的虚函数表
b、构造派生类实例成员变量
c、执行派生类构造函数
析构时则是按相反的顺序,就像这样:
第一步:析构派生部分(递归的)
a、(实例已经指向派生类的虚函数表)
b、执行派生类析构函数
c、析构派生类实例成员变量
第二步:析构基类部分(递归的)
a、让实例指向基类的虚函数表
b、执行基类析构函数
c、析构基类实例成员变量
#include <iostream>
#include <unistd.h>
#include <stdio.h>
using namespace std;
class Base
{
public:
Base()
{
printf("base constructor virtual pointer %p\n", (void*)*(int64_t*)((void*)this));
}
~Base()
{
printf("base destructor virtual pointer %p\n", (void*)*(int64_t*)((void*)this));
}
virtual void doIt() const = 0;
};
class Derived : public Base
{
public:
Derived()
{
printf("derived constructor virtual pointer %p\n", (void*)*(int64_t*)((void*)this));
}
void doIt()const {return ;};
~Derived()
{
printf("derived destructor virtual pointer %p\n", (void*)*(int64_t*)((void*)this));
}
};
int main()
{
Derived* const base = new Derived();
delete base;
}
执行结果
出现该问题
1、在基类的构造函数里直接调用虚函数
2、在基类的析构函数里直接调用虚函数
3、 在基类的构造函数里间接调用虚函数
4、在基类的析构函数里间接调用虚函数
5、使用野指针调用虚函数
通过排查代码,确认函数中没有出现前四种状况。
大概率是情况5,野指针的使用。
当前程序是 linux运行,程序大致逻辑是开辟线程,线程内部一直对资源进行访问,程序执行后没有对线程进行销毁 ,导致线程内部对已经释放的资源进行访问。
。
解决方案:
优雅的退出线程。
#include<stdio.h>
#include<stdlib.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
int i=1;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
/*同步取消,等到下一个取消点再取消*/
// pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
/*异步取消, 线程接到取消信号后,立即退出*/
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
printf("thread start \n");
while(1)
{
i++;
}
return (void *)0;
}
int main()
{
void *ret=NULL;
int iret=0;
pthread_t tid;
pthread_create(&tid,NULL,thread_fun,NULL);
sleep(1);
pthread_cancel(tid);//取消线程
pthread_join(tid, &ret);
printf("thread 3 exit code %d\n", (int)ret);
/*do something to clean*/
return 0;
}
设置为异步取消,在pthread_join后删除资源。
也可设置同步取消
#include<stdio.h>
#include<stdlib.h>
#include <pthread.h>
void cleanup(void*)
{
printf("clean\n");
}
void *thread_fun(void *arg)
{
int i=1;
pthread_cleanup_push(cleanup,NULL); //释放线程内的内存,可以防止内存泄漏
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
/*同步取消,等到下一个取消点再取消*/
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
printf("thread start \n");
while(1)
{
i++;
// printf("aaa\n"); //可作取消点 凡有阻塞作有的POSIX C函数都会有取消点, (具体包括哪些,可以查看man 7 pthreads)
pthread_testcancel(); //手动建立一个取消点, 防止线程中无取消点,导致线程不取消 //如果屏蔽所有的取消点.主程序就会堵在pthread_join里.
// printf("bbb\n");//可作取消点
// printf("ccc\n");//可作取消点
// sleep(5);//可作取消点
// printf("ddd\n");//可作取消点
// pthread_testcancel();//可作取消点
// system("ls");//可作取消点
}
return (void *)0;
}
int main()
{
void *ret=NULL;
int iret=0;
pthread_t tid;
pthread_create(&tid,NULL,thread_fun,NULL);
sleep(1);
pthread_cancel(tid);//取消线程
pthread_join(tid, &ret);
printf("thread 3 exit code %d\n", (int)ret);
return 0;
}
同步取消可在clear函数中清除数据