【问题标题】:C++ program stops producing console output upon input to the consoleC++ 程序在输入到控制台时停止生成控制台输出
【发布时间】:2018-08-02 07:05:15
【问题描述】:

我有一个 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;
}

【问题讨论】:

  • 您确定这是您的程序的问题,而不是控制台本身的问题吗?当输出停止时你实际上在做什么?以防万一不是控制台而是您的程序,尝试创建一个Minimal, Complete, and Verifiable Example,您可以向我们展示。
  • 我知道这一点。 (它也把我困住了一次。)这意味着在控制台中有一个选择正在等待。按 [ESC] 或 [ENTER] 中止/完成待定选择。虽然选择待申请停止,但您在调试器中看到它正在等待某些锁...可能是“Win32”代码。 span>
  • 我什至不知道您可以禁用应用程序中的选择。我刚刚找到了一个关于此的 Q/A:SO: How to disable user selection in Windows console.
  • 谢谢,这是最简单的例子:#include int main() { int i = 0; while (true) { i++; if (i%100000000 == 0) std::cout 选择 -> 按转义键 -> 程序继续运行(我在任务管理器中看到),但没有输出
  • 我可以做得更短:#include &lt;iostream&gt; int main() { for (;;) std::cout &lt;&lt; '.' &lt;&lt; std::flush; return 0; }。 ;-)(我不确定,但我相信 &lt;&lt; flush 实际上没有必要。)

标签: c++ console output interaction


【解决方案1】:

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

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

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

我单击控制台窗口并停止输出(如 OP 所述)。按 ESC 后,我期待更多的数字,但什么也没发生。

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

于是,我开始应用另一个 Q/A SO: How to disable user selection in Windows console 的解决方案。尽管如此,它似乎是值得的,没有MCVE。所以,我不得不使用 google 和 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;
}

这有效 - 禁用快速编辑。 (单击控制台窗口不再停止输出。)

但是,如果没有这个技巧,它也应该可以工作。 (我不明白这一点很困扰我。)想了一会儿,我想到了一个启发性的想法。难道std::cout在QuickEdit之后是bad()

所以,我制作了第三个版本。因为我不能使用coutput,所以我修改了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;
}

在快速编辑选择和 ESC 之后,i 一直是0。因此,另一个解决方法很明显:std::cout 应该定期为clear()ed:

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

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

  • 前者侵入性最小(只是在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;
}

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

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

【讨论】:

  • 刚刚找到第三种解决方案 - 答案扩展
  • 非常感谢您的帮助!
  • 当 QuickEdit 模式(例如 Mark)处于活动状态时,线程方法对我不起作用。 cout 仍然会冻结。 MSVC2015/Win10
  • @md_nr_gen 只要在控制台中标记了某些内容,这就是正确的。如果您在控制台中完成复制或使用 [ESC] 中止标记会发生什么?在这种情况下输出是否继续? (就我而言,确实如此。我已经在 Windows 10 上使用 VS2013 和 cmd 进行了测试。)顺便说一句。该示例仅涵盖std::cout。如果您的输出转到std::cerr,则解决方案可能也需要考虑这一点。 (未测试。)
  • @md_nr_gen 关于std::coutstd::cerr:我刚刚修改了程序,使其交替打印在std::coutstd::cerr 上。在控制台中标记一些文本并按 [ESC] 后,错误输出被跳过。只是,std::cout 工作正常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-12
  • 1970-01-01
相关资源
最近更新 更多