【问题标题】:Correctly exit forked process in C++在 C++ 中正确退出分叉进程
【发布时间】:2015-05-20 17:56:20
【问题描述】:

阅读How to end C++ code 的答案,我了解到从C++ 代码调用exit 是不好的。但是,如果我派生了一个子进程,该子进程必须在 某处 结束,并且在调用堆栈中如此深入以至于无法将其退出代码传递给 main 怎么办?

我找到了一些替代方法 - 诚然,这已经变得有点冗长,但请耐心等待:

#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>

#include <cstdlib>

#include <memory>
#include <string>
#include <iostream>

thread_local char const* thread_id{"main"};

struct DtorTest {
    std::string where{};
     DtorTest(void) = default;
     DtorTest(std::string _where) : where{std::move(_where)} {}
    ~DtorTest(void) {
        std::cerr << __func__ << " in " << thread_id << ", origin " << where << std::endl;
    }
};

std::unique_ptr< DtorTest > freeatexit{nullptr};

pid_t
fork_this(pid_t (*fork_option)(void))
{
    DtorTest test(__func__);
    return fork_option();
}

pid_t
fork_option0(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    std::exit(EXIT_SUCCESS);
}

pid_t
fork_option1(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    std::_Exit(EXIT_SUCCESS);
}

pid_t
fork_option2(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    {
    thread_id = "child";
    DtorTest test(__func__);
    }
    std::_Exit(EXIT_SUCCESS);
}

pid_t
fork_option3(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    throw std::exception();
}

int main(int argc, char const *argv[])
{
    int status;
    const int option = (argc > 1) ? std::stoi(argv[1]) : 0;
    pid_t pid;
    freeatexit = std::unique_ptr< DtorTest >(new DtorTest(__func__));


    switch (option) {
      case 0:
        pid = fork_this(fork_option0);
        break;
      case 1:
        pid = fork_this(fork_option1);
        break;
      case 2:
        pid = fork_this(fork_option2);
        break;
      case 3:
        try {
            pid = fork_this(fork_option3);
        } catch (std::exception) {
            return EXIT_SUCCESS;
        }
        break;
      case 4:
        try {
            pid = fork_this(fork_option3);
        } catch (std::exception) {
            std::_Exit(EXIT_SUCCESS);
        }
        break;
      default:
        return EXIT_FAILURE;
    }

    waitpid(pid, &status, 0);

    return status;
}

选项 0

可能是最糟糕的:

./a.out 0
~DtorTest in main, origin fork_this
~DtorTest in child, origin main
~DtorTest in main, origin main

问题是,fork_option0 中的test 的析构函数没有被调用,因为std::exit 只是忽略了任何具有自动存储功能的对象。更糟糕的是,unique_ptr 析构函数被调用两次

选项 1

./a.out 1
~DtorTest in main, origin fork_this
~DtorTest in main, origin main

fork_option1 中的析构函数也存在同样的问题,因为 std::_Exit 也忽略了自动存储。至少 unique_ptr 析构函数只被调用一次。

选项 2

这似乎有效,析构函数被正确调用。

./a.out 2
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option2
~DtorTest in main, origin main

选项 3

这是最接近从主通知返回建议的近似值,但是它有几个问题:

./a.out 3
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option3
~DtorTest in child, origin fork_this
~DtorTest in child, origin main
~DtorTest in main, origin main

尽管fork_option3 中的析构函数被正确调用,两次 双重释放发生了。首先是unique_ptr,其次是fork_this 中的对象。

选项 4

./a.out 4
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option3
~DtorTest in child, origin fork_this
~DtorTest in main, origin main

比选项三略好,因为unique_ptr 的双重释放已经消失。但是fork_this 中的对象仍然是双重释放的。

那么退出/结束子进程的正确方法是什么?

从上面的实验看来,选项 2 效果最好。但是,我可能错过了std::_Exit 的其他问题(请参阅How to end C++ code

【问题讨论】:

  • 你为什么不直接打电话给return
  • @Galik 孩子的工作已经完成,应该不复存在了。
  • 如果它的自动变量的析构函数没有被调用,子的工作就没有完成。
  • @Galik 我同意这一点。但是,我不希望子进程在 main 中继续。
  • 您可能想查看How to end C++ code?,但您可能有与那里假设的情况不同的情况,您可能需要查看一个或多个交叉引用的问题以适应您的情况。

标签: c++ fork destructor exit double-free


【解决方案1】:

这是fork 的传统模式。

#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>

#include <iostream>
#include <fstream>

struct DtorTest {
    ~DtorTest(void) { std::cout << "d'tor never runs\n"; }
};

int
child(void)
{
    // only child
    DtorTest dtortest;  // D'tor never runs
    std::ofstream fout("inchild.txt");  // file not flushed
    fout << "this is in the child\n";
    return 0;
}

int
main(void)
{
    pid_t pid;
    if ((pid = fork()))
      int status;
      waitpid(pid, &status, 0);
      return status;
   } else {
      return child();
   }
}

不要将extern "C" 用于系统包含文件。如果你需要它,那么你一定是在使用一个古老的编译器,而且所有的赌注都没有了。

【讨论】:

  • 我的例子是简化版。分叉发生在调用堆栈的更深处,因此无法将退出代码返回给 main。
  • 请查看我更新帖子中的选项 3 和 4。它表明您提出的解决方案不起作用。
猜你喜欢
  • 2015-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-02
  • 2014-04-28
  • 2013-08-01
  • 1970-01-01
相关资源
最近更新 更多