【问题标题】:How do you know whether main has exited?你怎么知道main是否已经退出?
【发布时间】:2015-05-14 08:54:47
【问题描述】:

在 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退出后,调用Catexit处理程序,如果类加载器没有卸载JNI共享库,则不调用JNI_OnUnload

由于共享库的对象可以通过显式销毁(并且应该释放它们的资源)和退出时的清理来销毁,我需要安全地区分这两种情况,因为当我们到达出口时 JVM 已经消失了代码!基本上没有一点嗅探,我无法在 JNI 规范/文档中找到一个共享库来知道 JVM 是否仍然存在,如果它消失了,那么尝试释放我们拥有的引用肯定是错误的到 Java 对象。

【问题讨论】:

  • 你能解释一下这个用例吗?
  • 在 main 中创建一个记录其销毁的对象。如果它是第一个创建的对象,它的析构函数将是 main 返回之前调用的最后一个函数。
  • @Benjamin 我记得在main 中创建的对象不会在exit 中被破坏,只有全局和局部静态?那么您是否建议在 main 内部使用本地静态 - 在这种情况下,这与我的解决方案几乎相同。
  • 为什么不在main 中使用本地非静态对象来检测您是否要离开main
  • 我这里没有“现代 C++ 设计”,但 Andrei Alexandrescu 在他的书中对这类 post-main 问题进行了详细分析。

标签: c++ static posix main exit


【解决方案1】:

这里真正的问题是您列出的所有权语义是混乱的。 JVM 有点拥有您的共享库,但也有点不拥有。你有一堆对 Java 对象的引用,有时你需要清理它们,但有时你不需要。

这里真正的解决方案是不将 Java 对象的引用保留为全局变量。然后,无论出于何种原因卸载库时,您都不需要知道 JVM 是否仍然存在。只需保留 Java 引用的对象内部对 Java 对象的引用,然后让 JVM 关心是否需要释放它们。

换句话说,首先不要让自己负责清理退出。

【讨论】:

    【解决方案2】:

    您的观察者不需要依赖任何静态初始化顺序:

    #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;
    }
    

    【讨论】:

    • 不错的尝试,但如果exit 被调用怎么办?不过你是对的,如果你可以修改 main,那么就可以在调用所有静态析构函数之前检测到 main 的退出。
    • 如果调用了exit 函数,则程序被认为在没有main 返回的情况下终止。您的标题可以通过两种方式阅读;你的意思是“你怎么知道 main 是否已经返回?”,或者“你怎么知道 atexit 处理程序是否已被执行?” (或者别的什么——有办法在不运行 atexit 处理程序的情况下终止程序?)
    • 请注意,通常静态析构函数不需要使用atexit 机制
    • 似乎确实有some issues 围绕 gcc,以及共享库中静态对象的析构函数。
    • 对于我必须支持的所有六个 Unix 平台,libc 都支持静态析构函数——该问题中的发布者使用的是未指定的平台,并且可能会做一些奇怪的事情。在 linux 和 OS X 上,静态对象的析构函数当然没有问题。对不起,标题有点模棱两可;不过,第一段清楚地说明了问题:“有没有办法找出我们是否在退出序列内?”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多