我们应该具体使用assert()
功能的地方有哪些?如果确定整数值是否大于零或指针是否为空,我们可以简单地使用私有函数来检查。在这种情况下,我们应该在哪里使用assert()
而不是自定义书面检查?
答案 0 :(得分:36)
上下文:我为了生活而编写服务器软件,这种软件在加载下一个版本之前会持续数周。因此,我的答案可能会与高度防御性的代码联系起来。
原则。
在我们深入研究使用assert
的具体位置之前,了解其背后的原理非常重要。
assert
是防御性编程中必不可少的工具。它有助于验证假设(实际断言它们),从而捕获编程错误(以区别于用户错误)。 assert
的目标是检测错误的情况,通常不能立即恢复。
示例:
char const* strstr(char const* haystack, char const* needle) {
assert(haystack); assert(needle);
// ...
}
<强>替代品。强>
在C?没有其他选择。除非您的函数被设计为能够传递错误代码或返回标记值,并且这是适当记录的。
在C ++中,异常是一种完全可以接受的替代方案。但是,assert
可以帮助生成内存转储,以便您可以在检测到错误情况时(这有助于调试)确切地查看程序所处的状态,而异常将展开堆栈,从而丢失上下文(oups ...)。
另外,一个异常可能(不幸的是)被高级处理程序捕获(或者来自其他开发人员的令人讨厌的捕获(你当然不会这样做)),在这种情况下你可能会完全错过错误,直到为时已晚。
不使用的地方。
首先,应该理解assert
仅在 Debug 代码中有用。在Release中,定义了 NDEBUG ,并且不生成任何代码。作为推论,发布assert
中的与评论具有相同的价值。
其次,应该理解,畸形的输入是你生活的一部分。您希望每次发生错误时编译器都显示assert
消息吗?哼!因此:
第三,应该理解崩溃是不赞赏。您的程序预计会顺利运行。因此,人们不应该试图在发布模式下保持断言:发布代码最终会在最终用户手中结束,永远不会崩溃。在最坏的情况下,它应该在显示错误消息时关闭。 期望在此过程中没有用户数据丢失,如果在重新启动用户时将其带回到原来的位置,那就更好了:例如,现代浏览器就是这样做的。
注意:对于服务器代码,在“点击”断言时,我们设法在大多数情况下重新处理下一个查询。
在何处使用。
assert
在调试模式下打开,因此应该用于调试。每当您测试新代码时,无论何时运行测试套件,只要软件在您的(或您的团队成员)手中,只要软件在您的QA部门手中。断言可让您发现错误并为您提供错误的完整上下文,以便您修复。
更好。由于您知道代码不会在Release中执行,因此您可以负担得起执行昂贵的检查。
注意:您还应该测试Release二进制文件,如果只是为了检查性能。
并在发布?
好吧,在我处理的代码库中,我们用特定的异常替换廉价的断言(其他被忽略),这些异常只会被记录问题的高级处理程序捕获(带有回溯),返回预编码的错误响应并恢复服务。开发团队会自动得到通知。
在部署的软件中,我看到的最佳实践意味着创建内存转储并将其流回开发人员进行分析,而尝试不丢失任何用户数据并且表现得像可能对不幸的用户。当我考虑到这项任务的难度时,我觉得在服务器端工作真的很幸运;)
答案 1 :(得分:8)
我要抛弃我对assert()
的看法。我可以在其他地方找到assert()
所做的事情,但stackoverflow提供了一个很好的论坛,可以就如何以及何时使用它提出建议。
assert
和static_assert
都提供类似的功能。假设你有一些函数foo
。例如,假设您有一个假定其参数不为null的函数foo(void*)
:
void foo(void* p) {
assert(p);
...
}
你的功能有几个人关心它。
首先,调用您的函数的开发人员。他可能只是看看你的文档,也许他会错过关于不允许空指针作为参数的部分。他可能不会读取函数的代码,但是当他在调试模式下运行它时,断言可能会捕获他对函数的不当使用(特别是如果他的测试用例很好)。
第二个(也是更重要的),是读取代码的开发人员。对他来说,你的断言说在这一行之后,p不为空。这有点被忽略了,但我相信是assert
宏最有用的功能。它记录了并强制执行条件。
只要可行,您应该使用assert
来对此信息进行编码。我喜欢把它想象为“在代码中的这一点,这是真的”(它以某种方式说明所以比评论强得多) 。当然,如果这样的陈述实际上没有传达太多/任何信息,那么就不需要它。
答案 2 :(得分:4)
我认为有一个简单而有力的观点:
assert ()
用于检查内部一致性。
用它来检查前置条件,后置条件和不变量。
当由于外部因素导致可能存在不一致时,代码无法在本地控制的情况,则抛出异常。 例外情况是在前提条件下无法满足后置条件的时候。好的例子:
new int
可以达到其先决条件,因此如果内存不可用,则抛出是唯一合理的响应。 (malloc
的后置条件是“有效指针或NULL ”) assert
不应用于上述内容。相比之下,
void sort (int * begin, int * end) {
// assert (begin <= end); // OPTIONAL precondition, possibly want to throw
for (int * i = begin, i < end; ++i) {
assert (is_sorted (begin, i)); // invariant
// insert *i into sorted position ...
}
}
检查is_sorted
正在检查算法是否正确给定其前提条件。例外不是一个合理的回应。
简而言之:assert
用于在程序本地正确的情况下永远不会发生的事情,即使代码正确也会出现问题。
无效输入是否触发异常是一种风格问题。
答案 3 :(得分:2)
如果您希望程序中止并在布尔条件不为真时显示运行时错误,则通常使用它。它通常使用如下:
void my_func( char* str )
{
assert ( str != NULL );
/* code */
}
它也可以用于在失败时返回NULL指针的函数:
SDL_Surface* screen = SDL_SetVideoMode( 640, 480, 16, SDL_HWSURFACE );
assert ( screen != NULL );
确切的错误消息assert()
给出,取决于你的编译器,但它通常是这样的:
Assertion failed: str, mysrc.c, line 5