在更改文件描述符 1 以引用不同的文件后,我应该如何管理 ::std::cout?

How should I manage ::std::cout after changing file descriptor 1 to refer to a different file?

我想做 dup2(fd, 1); close(fd); 并让 ::std::cout 写入新的 fd 1。我怎样才能重置 ::std::cout 的状态,所以没有什么有趣的?例如,预先冲洗是否足够?或者还有更多事情要做?

我也很好奇 ::std::cin

如果您更改了它们在它们下面使用的文件描述符,是否有用于重置它们的标准机制?

明确地说,我的目标基本上是将我自己的输入和输出重定向到其他地方。我不想让进程无意中在其 parent 的标准输出上打嗝或试图从其 parent 的标准输入中消耗任何东西。我再也不想碰我的 parent 的标准输入或标准输出了。我想忘记他们曾经存在过。

我尤其不想无意中将输出发送到我 parent 在不同文件描述符上使用的同一设备。

我的目标是让 cin 和 cout 通向与进程开始时完全不同的地方,并且永远不会以任何方式触及它们过去通向的地方。曾经!

您可以为 socket_streambuf class 创建(或使用现有的)库并将其关联到 std::cout/std::cin:

socket_streambuf<char> buffer{ "127.0.0.1:8888" }; // will call socket(), connect() or throw on failure
std::cout.rdbuf(&buffer); // re-direct cout to the network connection
std::cout << "Hello, World!\n"; // may call send() on basic_streambuf::overflow()

这样,您就不必为操纵(全局)C 流缓冲区的状态而烦恼。

选项 1:设置标准输入和标准输出

根据cppreference.com

By default, all eight standard C++ streams are synchronized with their respective C streams.

只要您没有明确调用 sync_with_stdio(false),它们就会保持这种状态。这是什么意思?以下:

In practice, this means that the synchronized C++ streams are unbuffered, and each I/O operation on a C++ stream is immediately applied to the corresponding C stream's buffer. This makes it possible to freely mix C++ and C I/O.

所以,flush()-ing 你的 cincoutdup()-ing 之前就足够了,因为它们应该处于一致的状态。

例如,如果您希望使用文件,您可以使用:

if (freopen("input.txt", "r", stdin) == NULL) {
    // Handle error, errno is set to indicate error
}

if (freopen("output.txt", "w", stdout) == NULL) {
    // Handle error, errno is set to indicate error
}

注意 1: 设置全局 extern FILE * stdinstdout 将不起作用,因为它只是将指针的单个实例更改为相关 FILE os 的结构。在此更改之前的任何时刻复制此指针的任何模块将继续使用旧的 FILE。一个具体的例子是 libc++ 对 cout 的实现,它在对象的初始化期间将 FILE * stdout 复制到私有成员。另一方面,freopen 更改 OS 的内部 FILE 结构以使用新打开的文件,影响任何拥有 FILE * 的人。

注意 2: 当使用 dup() 风格(而不是 freopen())时,我们正在改变基础 fd,而不是FILE*freopen() 方法的作用不止于此。来自 POSIX:

The freopen() function shall first attempt to flush the stream associated with stream as if by a call to fflush(stream). Failure to flush the stream successfully shall be ignored. If pathname is not a null pointer, freopen() shall close any file descriptor associated with stream. Failure to close the file descriptor successfully shall be ignored. The error and end-of-file indicators for the stream shall be cleared.

dup()-ing 可能有效,但是,它可能棘手,因为它won't affect other properties of the FILE*,包括:字符宽度,缓冲状态,缓冲区,I/O,Binary/text模式指示器,文件结束状态指示器,错误状态指示器,文件position indicator & (After C++17) Reentrant lock used to prevent data races.

当possible时,我建议使用freopen。否则,您可以按照自己描述的步骤进行操作 (fflush()clearerr())。跳过 fclose() 是明智的,因为我们无法通过任何 API 方法重新打开相同的内部 FILE

选项2:设置cin的&cout的rdbuf()

反过来,就像一些评论 proposed 一样,正在使用 rdbuf() 替换 cincout 的底层缓冲区。

你有什么选择?

  • 文件流: 打开 ifstream & ofstream 并使用它们:

    std::ifstream fin("input.txt");
    if (!fin) {
        // Handle error
    }
    cin.rdbuf(fin.rdbuf());
    
    std::ofstream fout("output.txt");
    if (!fout) {
        // Handle error
    }
    cout.rdbuf(fout.rdbuf());
    
  • 网络流: 使用 boost 的 boost::asio::ip::tcp::iostream(它源自 std::streambuf,因此可以工作) :

    boost::asio::ip::tcp::iostream stream("www.boost.org", "http");
    if (!stream) {
        // Handle error
    }
    
    cin.rdbuf(stream.rdbuf());
    cout.rdbuf(stream.rdbuf());
    
    // GET request example
    
    cout << "GET /LICENSE_1_0.txt HTTP/1.0\r\n";
    cout << "Host: www.boost.org\r\n";
    cout << "Accept: */*\r\n";
    cout << "Connection: close\r\n\r\n";
    cout.flush();
    
    std::string response;
    std::getline(cin, response);
    
  • 自定义流:std::streambuf 使用您自己的自定义包装器。请参阅示例 here.