C++ 程序在输入到控制台时停止生成控制台输出

use*_*798 4 c++ console interaction output

我有一个 C++ 程序(MSVC 2017),它通过 std::cout 不断输出调试信息。然而,有时当我与控制台进行物理交互时(例如意外点击它),它会停止产生输出。这意味着没有打印任何内容,尽管程序继续运行并完成正确执行的任何操作。

任何想法如何解决这一问题?使用“std::cout.setf(std::ios::unitbuf);”删除 std::cout 缓冲区 没有效果。

样本:

#include <iostream>

int main()
{
  int i = 0;
  while (true) {
    i++;
    if (i%100000000 == 0) std::cout << i++ << "\n";
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

Sch*_*eff 5

这就是我为重现测试所做的 - 写作mcve.cc

#include <iostream>
int main()
{
  for (char i = 0;; ++i) std::cout << (int)i << std::flush;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我在VS2013(调试模式)编译并启动。它开始“吹”出数字。

我点击进入控制台窗口,输出停止(如 OP 所述)。按下 后ESC,我预计会有更多数字,但什么也没发生。

我暂停了调试并查看了调用堆栈,但没有任何异常。一步一步,甚至看起来代码还在执行。(i正如我在调试器的自动显示中看到的那样,计数仍然发生。)

所以,我开始应用另一个 Q/A SO: How to disable user selection in Windows console的解决方案。尽管如此,它似乎值得它没有MCVE。所以,我不得不使用谷歌和 MSDN 来完成它:

#include <iostream>
#include <Windows.h>
int main()
{
  // disable QuickEdit mode in Console
  HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
  DWORD prev_mode;
  GetConsoleMode(hInput, &prev_mode); 
  SetConsoleMode(hInput, prev_mode & ~ENABLE_QUICK_EDIT_MODE);
  // start test
  for (char i = 0;; ++i) std::cout << (int)i << std::flush;
  // done (never reached)
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

这有效 - QuickEdit 已禁用。(单击控制台窗口不再停止输出。)

但是,如果没有这个技巧,它也应该可以工作。(我不明白这一点让我很困扰。)经过思考,我得出了一个启发性的想法。难道std::coutbad()在 QuickEdit 之后?

所以,我做了第三个版本。由于我无法使用cout我修改的put i,我可以在调试器中观看。(实际上,std::cout::good()也显示了返回,但分配给i它更能说明问题。)

#include <iostream>
#include <Windows.h>
int main()
{
  for (char i = 0;; ++i) {
    if (!std::cout.good()) i = 0;
    std::cout << (int)i << std::flush;
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

在 QuickEdit 选择和之后ESCi不断地0。因此,另一个解决方法是显而易见的:std::cout应该clear()定期编辑:

#include <iostream>
#include <Windows.h>
int main()
{
  for (char i = 0;; ++i) {
    if (!std::cout.good()) std::cout.clear();
    std::cout << (int)i << std::flush;
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我不确定我更喜欢这两种解决方案中的哪一种:

  • 前者的侵入性最小(只是 开头的一个补充main())。
  • 后者是我通常更喜欢的纯 C++(没有特定于平台的代码)。

得到关于非 Windows 平台的评论会很有趣......

我不记得我曾经在 Linux(也不是 Irix 或 Solaris——我过去使用过的操作系统)上看到过这样的 QuickEdit 问题。在那个系统上,选择由 Xterm/X11 处理(在我的例子中)——超出了流 I/O 的范围。

那么,是否有可能std::cout在该系统上变得糟糕(假设输出中没有编码错误)?


最后,我找到了一种可移植的非侵入性方法(以多线程为代价):

#include <atomic>
#include <iostream>
#include <thread>

int main()
{
  // spawn extra thread to clean cout periodically
  std::atomic<bool> exitThreadClearCOut = false;
  std::thread threadClearCOut([&]() {
    while (!exitThreadClearCOut) {
      if (!std::cout.good()) std::cout.clear();
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
      // 100 ms - nearly non-perceptable for humans but an "eternity" for modern CPUs
    }
  });
  // start main work
  for (char i = 0;; ++i) {
    std::cout << (int)i << std::flush;
  }
  // finish/join thread to clean cout periodically
  exitThreadClearCOut = true;
  threadClearCOut.join();
  // done
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

它会启动一个额外的线程来定期检查/清理std::cout. 这是其他必须添加的东西main()(我认为是“非侵入性修复”)——不需要更改实际的代码库。

注意:我有点怀疑并发访问std::cout是否安全(尽管我相信它是安全的)。关于这一点,我发现了另一个 Q/A SO:cout 是同步的/线程安全的吗?. 根据此链接中接受的答案,从 C++11 开始保证(或至少需要)。