104

我倾向于在我的 C++ 代码中添加大量断言,以便在不影响发布版本的性能的情况下更轻松地进行调试。现在,assert是一个没有考虑 C++ 机制的纯 C 宏。

另一方面,C++ 定义了std::logic_error,这意味着在程序逻辑出现错误的情况下抛出(因此得名)。抛出一个实例可能只是assert.

问题是assert两者abort都立即终止程序而不调用析构函数,因此跳过清理,而手动抛出异常会增加不必要的运行时成本。解决这个问题的一种方法是创建一个自己的断言宏SAFE_ASSERT,它的工作方式与 C 对应的一样,但在失败时抛出异常。

关于这个问题,我能想到三个观点:

  • 坚持 C 的断言。由于程序会立即终止,因此更改是否正确展开并不重要。此外,#define在 C++ 中使用 s 也同样糟糕。
  • 抛出异常并在 main() 中捕获它。允许代码在程序的任何状态下跳过析构函数是不好的做法,必须不惜一切代价避免,对 terminate() 的调用也是如此。如果抛出异常,则必须捕获它们。
  • 抛出异常并让它终止程序。 终止程序的异常是可以的,并且由于NDEBUG,这将永远不会在发布版本中发生。捕获是不必要的,它会将内部代码的实现细节暴露给main().

这个问题有确定的答案吗?有什么专业的参考吗?

编辑:当然,跳过析构函数没有未定义的行为。

4

5 回答 5

103
  • 断言用于调试。您的交付代码的用户不应该看到它们。如果一个断言被命中,你的代码需要被修复。

    CWE-617:可达断言

该产品包含可由攻击者触发的 assert() 或类似语句,这会导致应用程序退出或其他比必要更严重的行为。

虽然断言有助于捕获逻辑错误并减少达到更严重漏洞条件的机会,但它仍然可能导致拒绝服务。

例如,如果服务器同时处理多个连接,并且在一个连接中发生 assert() 导致所有其他连接被丢弃,则这是导致拒绝服务的可达断言。

  • 例外是针对特殊情况。如果遇到一个,用户将无法做她想做的事,但可能能够在其他地方恢复。

  • 错误处理适用于正常的程序流程。例如,如果您提示用户输入数字并得到无法解析的内容,这是正常的,因为用户输入不受您的控制,您必须始终理所当然地处理所有可能的情况。(例如循环直到你有一个有效的输入,在两者之间说“对不起,再试一次”。)

于 2012-08-21T20:21:45.450 回答
80

断言在 C++ 代码中是完全合适的。异常和其他错误处理机制并不是真正用于与断言相同的事情。

错误处理适用于当有可能很好地恢复或向用户报告错误时。例如,如果尝试读取输入文件时出错,您可能需要对此做一些事情。错误可能是由错误引起的,但它们也可能只是给定输入的适当输出。

断言用于检查 API 的要求是否在通常不会被检查的情况下得到满足,或者用于检查开发人员认为他通过构造来保证的事情。例如,如果一个算法需要排序的输入,你通常不会检查它,但你可能有一个断言来检查它,以便调试构建标记这种错误。断言应始终指示错误操作的程序。


如果您正在编写一个程序,其中不正常的关闭可能会导致问题,那么您可能希望避免断言。严格按照 C++ 语言的未定义行为在这里不属于这样的问题,因为命中断言可能已经是未定义行为的结果,或者违反了其他可能阻止某些清理正常工作的要求。

此外,如果您根据异常实现断言,那么它可能会被捕获并“处理”,即使这与断言的目的相矛盾。

于 2012-08-21T20:18:51.990 回答
14

断言可用于验证内部实现不变量,例如执行某些方法之前或之后的内部状态等。如果断言失败,则实际上意味着程序的逻辑已损坏,您无法从中恢复。在这种情况下,您能做的最好的事情就是尽快中断而不向用户传递异常。断言的真正好处(至少在 Linux 上)是核心转储是进程终止的结果,因此您可以轻松地调查堆栈跟踪和变量。这对于理解逻辑故障比异常消息更有用。

于 2012-08-21T20:35:44.897 回答
13

由于 abort() 而没有运行析构函数不是未定义的行为!

如果是这样,那么调用std::terminate()也是未定义的行为,那么提供它有什么意义呢?

assert()在 C++ 中和在 C 中一样有用。断言不是用于错误处理,而是用于立即中止程序。

于 2012-08-21T20:35:16.367 回答
5

恕我直言,断言用于检查如果违反的条件,则使其他一切变得无稽之谈。因此,您无法从它们中恢复,或者更确切地说,恢复是无关紧要的。

我会将它们分为两类:

  • 开发者罪过(例如返回负值的概率函数):

浮动概率(){返回-1.0;}

断言(概率()> = 0.0)

  • 机器坏了(例如,运行你的程序的机器非常错误):

诠释 x = 1;

断言(x > 0);

这些都是微不足道的例子,但与现实相差不远。例如,考虑使用向量返回负索引的朴素算法。或定制硬件中的嵌入式程序。或者更确切地说,因为sh*t 发生了

如果有这样的开发错误,你不应该对实施的任何恢复或错误处理机制有信心。这同样适用于硬件错误。

于 2014-12-16T18:37:57.027 回答