exit 设置错误码失败

exit fails to set error code

我有一个 C++ Windows 程序无法设置退出代码。该程序非常复杂,我目前无法用简单的测试用例重现它。我知道程序调用 exit(1) 因为我在该行上有一个断点。在我跨过它之后,调试器 (VS2010) 立即打印 The program program.exe has exited with code 0 (0x0). 当我 运行 它来自 shell 时,%ERRORLEVEL% 也设置为 0。

我使用 subsystem:console 和普通的旧 main(没有 WinMain)。

这只发生在 Windows Server 2008 R2 上,而不是在我的 Windows 8.1 笔记本电脑上。我在两者上 运行 使用相同的可执行文件。

我尝试使用 exit_exitExitProcessreturn(违规调用在 main 中),但是 none 其中似乎有任何效果。我也试过return其他代码,同样没有结果。

有一个 similar question 但我无法重现其中描述的结果。我的程序确实使用了线程。

我该如何解决这个问题?我有点莫名其妙。

I have tried to use exit, _exit, ExitProcess, and return

您排除了所有合理的解释,尤其是对 ExitProcess() 的解释。只剩下一种可能,您需要尝试 TerminateProcess()。如果 still 没有设置退出代码,那么您需要将该机器推出第 4 层 window.

但期望它现在可以工作。 ExitProcess() 和 TerminateProcess() 之间的区别在于前者确保所有 DLL 都收到终止通知。他们的 DllMain() 函数使用 fdwReason = DLL_PROCESS_DETACH 调用。这让 DLL 有机会做一些恶心的事情,比如调用 Exit/TerminateProcess() 本身,从而搞砸退出代码。

如果您没有所有源代码,则很难找到这样的 DLL。也可能是注射的,现在有太多了。最好的办法是在系统调用上设置一个断点,这样您就可以立即捕获它,无论如何您可能都想这样做。

进入 main() 后,使用“调试”>“新建断点”>“在函数处中断”并输入 {,,ntdll.dll}_NtTerminateProcess@8。按 F5 键,调试器现在会在程序终止前停止。看Call Stack找出作恶者

多线程程序中涉及 exit()、_exit()、ExitProcess() 和其他程序的奇怪症状 - 特别是如果症状因主机而异 - 有一种变量被不同线程修改或访问的味道,没有同步。

查看您链接到的另一个线程,您似乎正在使用易失性变量在线程之间进行通信,但没有使用任何形式的同步(例如,访问该变量值的代码和修改该变量值的代码该值需要通过关键部分、互斥锁或类似结构进行协作)。

这一点间接证据使气味更加强烈。

我怀疑的基本问题是将变量声明为 volatile 既没有必要也不足以确保变量始终具有对您的程序有意义的值。特别是,当修改仅部分完成时,阻止正在修改变量的线程被抢占,以及阻止另一个线程尝试访问或修改受影响的变量是不够的。

如果您查阅 Herb Sutter 的一些文章(特别是他的 "Guru of the Week" 系列中那些与线程同步有关的文章),您会发现为什么会这样的详细解释。其他作者也描述了这样的事情,但萨特的文章是我随手回忆的。

解决方案是引入一些同步方法,让程序中的每个线程在访问或修改它们之间共享的变量之前虔诚地使用它。这避免了会导致如您描述的症状的各种问题(竞争条件、操作在中途被抢占)。

此类问题很少通过调试器逐步解决。原因是这些症状是突发的 属性。在不同的执行线程中,几个不太可能且通常独立的事件必须同时发生。调试器通常会更改程序中事件的时间,而时间是出现症状的关键考虑因素。

选项包括使关键变量成为原子变量(因此特定操作不能被抢占)、临界区(线程在程序中显式协作的地方)或互斥体(根据定义,允许不同程序中的线程显式协作在访问共享内存之前)。

是的,这会在您的程序中引入瓶颈 - 每个线程都必须会合并可能相互等待的点。这会影响程序的吞吐量。有些人提倡使用 volatile 变量来避免这种担忧。通常情况下,结果是长 运行 程序出现间歇性症状,就像您在这个问题中描述的以及您链接到的 "similar question"。

无论您使用标准同步方式(例如在 C++11 中引入)还是 windows 特定方式(WIN API 函数)都没有关系。重要的是你使用了一种有意的同步方法,而不是仅仅让变量变易变。不同的同步选项有不同的权衡取舍,因此您需要根据程序的需求做出决定。

另一个考虑是向所有线程发出信号,以便它们干净地关闭,等待它们全部关闭,捕获它们的退出代码,然后退出程序。在线程 运行 main() 中执行此操作通常不太容易出错 - 最终启动进程,因此更有可能访问正确清理所需的信息。如果另一个线程决定程序需要退出,那么最好将需要返回给 main() 来执行。