1 概述
在写一个简单的校验工具时,意外的发现了在 C++中的 Main 函数中使用return和exit退出效果存在不同,这也导致了我的程序core dump了。在检查core文件的同时,我发现如果使用exit则可能导致析构顺序与预想的方式不一致,进而导致段错误。总结一句话:exit不会优先将主函数内的局部变量析构,因此在单例模式下可能会产生异常。
2 起因
事情起因是我复用了现有的代码结构,分出来了一个入口实现数据库参数校验的功能,用于程序启动前参数校验提醒。整体来看是这样的:
- OraOper 类,单例,有
init和final两个接口分别实现初始化和退出,有问题的是final接口内使用了logger输出日志。 - Logger 类,单例,有
init接口,实现初始化,没有主动析构入口,也就是默认程序退出时析构 以上是被复用的代码结构,因为一个main函数内存在多个出口(包含参数检查、连接检查、参数校验),因此一次一次调用OraOper::final是不现实的(不好看),因此我打算对其进行RAII封装,变成这样:
class OraOperRAII
{
public:
~OraOperRAII(){
if(m_init_flag){
OraOper::final();
}
}
public:
bool init(){
return OraOper::init();
}
std::unique_ptr<OraOper> get(){.....}
private:
bool m_init_flag{false};
};接着在 main 函数里实例化并使用
int main()
{
if(situation1){
exit(-1);
}
if(initLogger()){
exit(-1);
}
OraOperRAII raii;
if(!raii.init()){
exit(-1);
}
if(!check(raii.get(),...))
{
exit(-1);
}
return 0;
}我当时认为,当程序退出后,析构的循序是这样的:
- OraOperRAII
- ~Logger
因为 Logger 是单例,是一个全局的静态变量,理论上应该最后析构。但实际上查看 core 文件时发现是它先于
OraOperRAII析构,这明显不符合常理。
3 原因
在我查阅资料后,我发现有一篇博文分析了主函数return和exit的汇编,链接如下:
https://www.cnblogs.com/aquester/p/10333238.html
int main()
{
X x(1);
exit(0); // 和下一行二选一执行
return(0);
}在他的示例中,我们能注意到对于在使用return 0的场景中,在main结束时调用了X的析构,而exit则没有。
此时我就猜测,有没有一种可能,因为exit的原因导致了析构的优先级出现了异常,也就打破了原来的生命周期,使OraOperRAII和Logger的析构优先级平级?进而导致了异常?
4 总结原因
问题就在于单例之间的互相依赖(我知道不好,之后再想办法解决吧)导致其在析构时需要注意析构顺序。
而exit相比起return对原有的生命周期有一定的影响。
优先使用return!