在C和C++中,atexit
函数要么在exit
内部调用,要么在main
返回后调用(名义上称为exit
:__libc_start_main(argc,argv) { __libc_constructors(); exit(main(argc,argv)); }
)。
有没有办法找出我们是否在退出序列内?C++全局和局部静态的析构函数都注册atexit
,所以你的代码当然可以在这个阶段被调用。(有趣的是,在某些平台上,如果您尝试在 exit
内创建一个C++本地静态对象,它会在出口锁上死锁!
到目前为止,我最好的尝试如下:
static bool mainExited = false;
static void watchMain() {
static struct MainWatcher {
~MainWatcher() { mainExited = true; }
} watcher;
}
当你想监视退出时,你调用watchMain()
,mainExited
随时告诉你退出序列是否已经开始——当然,如果以后初始化的本地静态对象正在破坏!
是否可以改进该技术以纠正此问题,或者是否有另一种可行的方法?
除了 - 用例!
虽然从语言的角度来看,这个问题很有趣(有点像"我能判断我是否在catch
块内吗?"),但概述一个用例也很有用。我在编写一些代码时遇到了这个问题,这些代码将在加载和不加载 JVM(直接调用或通过 JNI 调用)的情况下运行。JVM 退出后,将调用 C atexit
处理程序,如果类装入器未卸载 JNI 共享库,则不会调用JNI_OnUnload
。
由于共享库的对象可以通过显式销毁(并且应该释放其资源)和在退出时清理来销毁,因此我需要安全地区分这两种情况,因为当我们到达退出代码时,JVM 已经消失了!基本上,如果没有一点嗅探,我就无法在共享库的 JNI 规范/文档中找到知道 JVM 是否仍然存在,如果它消失了,那么尝试释放我们对 Java 对象的引用肯定是错误的。
这里真正的问题是你列出的所有权语义被搞砸了。JVM有点拥有你的共享库,但也有点不拥有。你有一堆对 Java 对象的引用,有时你需要清理,但有时不需要。
这里真正的解决方案就是不保留对 Java 对象的引用作为全局变量。然后,无论出于何种原因卸载库时,您都不需要知道 JVM 是否仍然存在。只需保留从 Java 引用的对象内部对 Java 对象的引用,然后让 JVM 关心它是否需要释放它们。
换句话说,首先不要让自己负责退出时的清理。
观察程序不需要依赖任何静态初始化顺序:
#include <iostream>
struct MainWatcher // : boost::noncopyable
{
enum MainStatus { before, during, after };
MainWatcher(MainStatus &b): flag(b) { flag = during; }
~MainWatcher() { flag = after; }
MainStatus &flag;
};
//////////////////////////////////////////////////////////////////////
// Test suite
//////////////////////////////////////////////////////////////////////
// note: static data area is zero-initialized before static objects constructed
MainWatcher::MainStatus main_flag;
char const *main_word()
{
switch(main_flag)
{
case MainWatcher::before: return "before main()";
case MainWatcher::during: return "during main()";
case MainWatcher::after: return "after main()";
default: return "(error)";
}
}
struct Test
{
Test() { std::cout << "Test created " << main_word() << "n"; }
~Test() { std::cout << "Test destroyed " << main_word() << "n"; }
};
Test t1;
int main()
{
MainWatcher watcher(main_flag);
// rest of code
Test t2;
}