C ++程序是否应该捕获所有异常并防止异常冒泡过main()?


29

曾经有人告诉我,C ++程序最终应该捕获所有异常。当时给出的推理基本上是允许异常冒泡main()进入怪异的僵尸状态之外的程序。几年前有人告诉我,回想起来,我相信观察到的现象是由于有关项目中产生的异常大的堆芯产生了很长时间。

当时,这似乎很奇怪,但却令人信服。C ++应该“惩罚”程序员没有捕获所有异常完全是荒谬的,但是我面前的证据似乎支持了这一点。对于有问题的项目,引发未捕获异常的程序似乎确实进入了怪异的僵尸状态-或我怀疑是现在的原因,在意外的核心转储中的进程异常难以停止。

(对于任何想知道为什么这在当时不那么明显的人:该项目从多个进程的多个文件中生成了大量输出,这些输出有效地掩盖了任何形式的aborted (core dumped)消息,在这种情况下,对核心转储进行事后检查是没有必要的“这不是一项重要的调试技术,因此无需过多考虑核心转储。程序的问题通常不取决于寿命长的程序随时间推移从许多事件中累积的状态,而是取决于寿命短的程序的初始输入(< 1小时),因此使用来自调试版本或调试器的相同输入重新运行程序以获得更多信息更为实用。)

目前,我不确定仅出于防止异常离开的目的而捕获异常是否有任何主要的优点或缺点main()

我可以想到的允许异常冒泡的小好处main()是,它会导致将结果std::exception::what()打印到终端上(至少使用Linux上的gcc编译程序)。另一方面,通过捕获所有派生自std::exception该异常的异常并打印结果来实现此目的很简单std::exception::what(),如果希望从非派生的异常中打印一条消息,std::exception必须在离开之前对其进行捕获才能main()打印消息。

我可以想到的允许异常冒泡的适度缺点main()是,可能会生成不需要的核心转储。对于使用大量内存的过程而言,这可能会很麻烦,并且从程序控制核心转储行为需要特定于OS的函数调用。另一方面,如果需要核心转储和退出,则可以在任何时间通过调用来实现,而在std::abort()没有核心转储的情况下可以通过调用在任何时间实现std::exit()

有趣的是,我认为我what(): ...在崩溃时从未见过由广泛分布的程序打印的默认消息。

支持或反对允许C ++异常冒出来的强有力的论据是什么(如果有)main()

编辑:此站点上有很多常规异常处理问题。我的问题特别是关于无法处理的C ++异常,并使其一直存在main()-可能会显示错误消息,但这是立即显示的停止错误。




@gnat我的问题更加具体。它涉及无法处理的C ++异常,而不是任何情况下任何编程语言中的异常处理。
Praxeolitic

对于多线程程序,事情可能会更复杂或更奇特(错误例外)
Basile Starynkevitch

1
Ariane 5的软件专家当然希望他们在第一次发布时就抓住了所有例外……
Eric Towers

Answers:


25

允许异常通过main的一个问题是程序将以std::terminate默认行为为的调用结束std::abort。仅在调用之前完成堆栈展开操作时才定义实现,terminate这样程序就可以结束而无需调用单个析构函数!如果您确实需要通过析构函数调用来还原某些资源,那么您就很麻烦了...


12
如果您确实需要通过析构函数调用来还原某些资源,那么您就陷入了困境…… =>鉴于(不幸的)C ++非常容易崩溃,而且这还没有提及操作系统可能决定终止您的程序在任何时候(例如Unix中的OOM杀手),都需要对程序(及其环境)进行定制以支持崩溃。
Matthieu M.

@MatthieuM。您是说析构函数不是即使在崩溃期间也应该进行资源清理的好地方吗?
Praxeolitic

6
@Praxeolitic:我是说并不是所有崩溃都会使堆栈崩溃,例如std::abort不会崩溃,因此失败的断言也不会。还值得注意的是,在现代OS上,OS本身会清理与进程ID关联的许多资源(内存,文件句柄等)。最后,我还暗示了“深度防御”:不能期望所有其他过程都是防错的,并且会始终释放它们必须计划的资源(会话,完成写入文件等)。它...
Matthieu M.

3
@Praxeolitic不,您的软件在电源故障期间无需破坏数据。并且,如果您能够进行管理,则还可以在未处理的异常之后设法不破坏数据。
user253751 '16

1
@Praxeolitic:实际上,这确实存在。您将要收听WM_POWERBROADCAST消息。仅当计算机由电池供电(如果使用笔记本电脑或UPS)时,此方法才有效。
布赖恩

28

不让异常逃逸的主要原因main是,否则您将失去控制问题如何报告给用户的所有可能性。

对于不打算长时间使用或广泛分发的程序,可以以操作系统决定执行的任何方式报告意外错误(例如,在Windows上显示人脸错误对话框),这是可以接受的)。

对于您出售的程序或信誉良好的组织向公众提供的程序,最好以一种不错的方式报告您遇到了意外问题,并尝试保存尽可能多的程序,这是一个更好的主意。用户的数据。不会让用户损失半天的工作时间,也不会导致意外崩溃,但是半优雅地关闭它通常比使用替代方法对您的企业声誉要好得多。


您确定留下的C ++异常的默认行为main() 与操作系统有很大关系吗?操作系统可能不了解C ++。我的猜测是,它取决于编译器在程序中插入的代码。
Praxeolitic

5
@Praxeolitic:更多的是OS设置了约定,并且当程序意外终止时,编译器会生成代码来实现这些约定。最重要的是,应在合理可能的情况下避免程序意外终止。
Bart van Ingen Schenau,2016年

您只能在std C ++范围内进行控制。应该有实现这种“崩溃”的特定实现方式。C ++通常不会在真空中运行。
马丁·巴

您可以在注册表中针对您所需要的内容配置您的面部错误对话框-但您需要控制注册表才能执行此操作-而且大多数程序都不以信息亭模式运行(已接管操作系统)。
PerryC '02

11

TL; DR规范怎么说?


技术绕道...

当引发异常且没有处理程序准备就绪时:

  • 由实现定义,堆栈是否退绕
  • std::terminate 被调用,默认情况下中止
  • 根据您的环境设置,中止可能会或可能不会留下崩溃报告

后者对于很少发生的错误很有用(因为再现它们是一个浪费时间的过程)。


是否捕获所有异常最终取决于规范:

  • 是否指定了如何报告用户错误?(无效值, ...)
  • 是否指定了如何报告功能错误?(缺少目标目录,...)
  • 是否指定了如何报告技术错误?(断言激发,...)

对于任何生产程序,都应指定此名称,并且您应遵循规范(并可能会争辩说要对其进行更改)。

对于只供技术人员(您,您的队友)使用的快速组合程序,两者都不错。我建议让其崩溃,并根据您的需要设置环境以获取报告或不获取报告。


1
这个。决定性因素。基本上不在C ++的范围内。
马丁·巴

4

捕获到的异常使您有机会打印出精美的错误消息,甚至尝试从错误中恢复(可能只是通过重新启动应用程序)。

但是,在C ++中,异常不会在引发程序时保留有关程序状态的信息。如果捕获了该状态,则所有此类状态都将被忘记,而如果让程序崩溃,则该状态通常仍会存在,并且可以从程序转储中读取,从而更易于调试。

所以这是一个权衡。


4

当您知道必须中止时,请继续并致电std::terminate以减少任何进一步的损害。

如果您知道可以放心,请改为这样做。请记住,从不捕获异常就不能保证堆栈展开,因此捕获并重新抛出。

如果您可以安全地报告/记录错误,而不是系统自行解决错误,请继续。
但是,请确保不要在这样做的时候无意中使事情变得更糟。

当检测到不可恢复的错误时,保存数据通常为时已晚,尽管这取决于特定的错误。
无论如何,如果您的程序是为快速恢复而编写的,即使只是正常关机,将其杀死也可能是结束程序的最佳方法。


1

在大多数情况下,优雅地崩溃是一件好事-但是要权衡利弊。有时候崩溃是一件好事。我应该提到,我主要是在考虑大型调试。对于一个简单的程序-尽管这可能仍然有用,但它与非常复杂的程序(或几个交互的复杂程序)的帮助相去甚远。

您不希望在公共场合崩溃(尽管这确实是不可避免的-程序崩溃,而真正可数学验证的非崩溃程序不是我们在这里所说的)。在演示过程中想到Bill Gates的BSODing-不好吧?但是,我们可以尝试找出为什么崩溃而不以相同的方式再次崩溃。

我打开了Windows错误报告功能,该功能可在未处理的异常上创建本地崩溃转储。它可以解决您是否具有与(崩溃的)构建关联的符号文件的问题。有趣的是,因为我设置了此工具,所以我想使更多的崩溃—因为我从每次崩溃中学习。然后,我可以修复错误并减少崩溃。

因此,就目前而言,我希望我的程序一直运行到顶部并崩溃。将来,我可能想优雅地吃掉我所有的异常-但如果我可以让它们为我工作,那就不要。

如果您有兴趣,可以在这里阅读有关本地故障转储的更多信息:收集用户模式转储


-6

最终,如果异常通过main()冒泡,它将使您的应用程序崩溃,并且在我看来,应用程序永远都不应崩溃。如果可以将某个应用程序崩溃在一个地方,那么为什么不在任何地方?为什么还要烦恼异常处理呢?(讽刺,没有真正暗示这个……)

您可能有一个全局try / catch,它会打印一条优美的消息,告诉用户发生了不可恢复的错误,并且他们需要通过电子邮件向bob发送有关此错误或类似内容的电子邮件,但是崩溃是完全不专业和不可接受的。它很容易防止,您可以将发生的事情以及如何解决问题通知用户,以免再次发生。

严格来说,崩溃是业余时间。


8
因此,最好假装一切正常,然后用乱码覆盖所有永久存储吗?
Deduplicator

1
@Deduplicator:否:您始终可以捕获异常并进行处理。
Giorgio

5
如果你知道如何处理它,你可能会处理它,你起床不久main。如果您不知道如何处理,那么假装这样做显然是一个非常可怕的错误。
Deduplicator

8
但是崩溃是完全不专业且不可接受的 =>花时间在无用的功能上工作是不专业的:仅当对最终用户很重要时才崩溃,如果不重要则崩溃,然后使其崩溃(并获得崩溃报告,并带有内存转储)更经济。
Matthieu M.

Matthieu M.如果确实需要您付出很多努力来记录错误,保存所有打开的文件并在屏幕上绘制友好的消息,我不知道该说些什么。如果您认为正常失败是没有用的,那么您可能应该与正在为您的代码提供技术支持的任何人交谈。
罗杰·希尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.