在c++中正确退出分叉进程

Correctly exit forked process in C++

本文关键字:退出 分叉 进程 c++      更新时间:2023-10-16

阅读如何结束c++代码的答案,我了解到从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_option0test的析构函数没有被调用,因为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 h1> 似乎可以工作,析构函数被正确调用。
./a.out 2
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option2
~DtorTest in main, origin main
<标题>选项3

这是最接近main通知返回的,但是它有几个问题:

./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

比选项3稍微好一点,因为没有了unique_ptr的double free。然而,fork_this中的对象仍然是双自由的。

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

从上面的实验来看,选项2似乎效果最好。然而,我可能错过了std::_Exit的其他问题(参见如何结束c++代码)

这是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 runsn"; }
};
int
child(void)
{
    // only child
    DtorTest dtortest;  // D'tor never runs
    std::ofstream fout("inchild.txt");  // file not flushed
    fout << "this is in the childn";
    return 0;
}
int
main(void)
{
    pid_t pid;
    if ((pid = fork()))
      int status;
      waitpid(pid, &status, 0);
      return status;
   } else {
      return child();
   }
}

不要对系统包含文件使用extern "C"。如果您需要这样做,那么您一定使用了一个古老的编译器,并且所有的赌注都取消了。