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
!