使用assert()进行生产不比if..else ..块更受青睐?

时间:2012-01-30 05:08:03

标签: c coding-style if-statement assert

我发现使用assert(...)使我的代码更短更容易阅读,而不是冗长的if..else..块。但是,是否有良好的技术理由不在运费代码中使用assert(...),因为它与使用较少代码时测试return值的做法相同?

9 个答案:

答案 0 :(得分:4)

阅读this article后,我将分享我对assert

的看法
  1. 是的,当一些东西绝对符合你声称的条件时,可以使用assert

  2. 许多语言允许您在断言时引发自定义错误,C没有“异常”可能会产生错误,如果不直接查看相关来源,则会更难诊断。

答案 1 :(得分:4)

如果是编程错误(可能是来电者),请使用assert

如果 编程错误,请使用if / else并妥善处理此情况。

答案 2 :(得分:3)

IMO这里没有任何答案说最重要的部分:断言程序员的假设。 assert(x)表示“x此时总是如此,您可以检查是否需要”。如果用户看到断言错误,则表示您做错了。

你可能需要的是一个函数,它与assert几乎完全相同,但在发布和调试模式下都是如此。使用check(x)意味着“检查x是否为真。如果不是,请告诉用户并退出”

答案 3 :(得分:2)

assert()专为那些在理智情况下从未被假设的事物而设计。

if - else适用于在理智情况下有时可能出现错误的事情,因为else部分旨在优雅地处理此类情况。

简而言之,if语句旨在避免崩溃; assert()旨在导致

答案 4 :(得分:2)

虽然其他答案都有一些很好的信息,但似乎没有人(对我而言)直接解决了你提出的问题。

IMO,是的,将assert留在(大多数)生产代码中存在相当大的缺点:您无法控制它显示的错误消息,这通常看起来很可怕。

而不是像:

Fatal error: Assertion failed! x != NULL in xxx.c, line 107

我宁愿给客户这样的东西:

Please contact customer support and give them code xxx:107

答案 5 :(得分:2)

断言很好。编译时断言甚至更好。注意:

如果你的环境还没有静态断言,这是一个建议。

下面给出的ASSERT()宏可以放在代码中的任何位置,除了:

  • 在两次包含的头文件中,没有#ifndef...#endif包装。
  • 在结构定义(或枚举定义)的中间。
  • 在严格的C89或C90之后发表声明。 (但你可以把它包在大括号里!)

如果你想在结构定义的中间粘贴一些东西,你需要使用冗长,丑陋的三行构造#if...#error...#endif。如果你这样做,那么预处理器对于“常量表达式”的含义有一个很多更有限的概念。

这是对网络创意的改进,主要来自http://www.pixelbeat.org/programming/gcc/static_assert.html。此定义短于BOOST_STATIC_ASSERT()。而且,我相信,这比Linus关于改进BUILD_BUG_ON()的建议更好。您通常看到的do{...}while(0)包装器在此处完全不适用,因为它限制了允许的位置。

这比Google的COMPILE_ASSERT / CompileAssert更简单。 “sizeof bitfield”技巧似乎也很好,来自Linux的BUILD_BUG_ON_ZERO(),但是不是它无用的兄弟BUILD_BUG_ON()

有许多建议使用具有负索引的数组。但是对于GCC,大多数这些都没有检测到非常量 arg(这很容易做错误),除了'extern int foo [expression]',它也给出了'未使用的变量'警告。但是typedef int array[expression]似乎也很好:见下文。

定义:

#define CONCAT_TOKENS(a, b)     a ## b
#define EXPAND_THEN_CONCAT(a,b) CONCAT_TOKENS(a, b)
#define ASSERT(e) enum{EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__) = 1/!!(e)}

我认为,同样好的是以下变体,但它的长度超过五个字符:

#define ASSERT(e) typedef int EXPAND_THEN_CONCAT(ASSERT_line_,__LINE__)[1-2*!(e)]

还有do{switch(0){case 0:case(e):;}}while(0)构造,我没有调查过。

有时人们需要一个变体来处理这样的情况:两个不同的头文件偶然发生在同一行上有两个ASSERT(),或者同样适用于源文件和头文件。您可以通过__COUNTER__处理此问题,但某些编译器不支持这种情况(并且更加丑陋)。我们不能使用__FILE__,因为它通常不会扩展为有效的C令牌(例如,它有一个点c或点h)。 Mozilla版http://mxr.mozilla.org/mozilla-central/source/mfbt/Assertions.h指出这种冲突“应该是罕见的”,但是当它们发生时它们会极大地惹恼你的队友。这也可用于处理多行宏中的多个ASSERTS,其中__LINE__不会更改。

#define ASSERTM(e,m) enum{EXPAND_THEN_CONCAT(m##_ASSERT_line_,__LINE__)=1/!!(e)}

下一个变体ASSERT_zero(),类似于使用'sizeof bitfield'技巧的BUILD_BUG_ON_ZERO(),。这会产生:

  • 编译错误,e为false或
  • 零值。

因此它可以在语句不能使用的地方使用,例如在表达式的中间。

#ifndef __cplusplus
#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // cf. BUILD_BUG_ON_ZERO(), !C++
#else
#define ASSERT_zero(e) (!sizeof(char[(e) ? 1 : -1])) // careful: g++ has VLAs
#endif

答案 6 :(得分:2)

我认为使用assert表示运行时错误的形式很差,特别是因为它违反了惯例:

  • assert通常是从非调试版本编译而来的。现在,可能会选择编译所有版本,期望assert始终处于启用状态,但是后来继承您代码的任何人都可能不理解。如果您编写代码,assert具有副作用(例如assert((fp = fopen(filename, "r")) != NULL)),情况会更糟。

  • 同样,assert旨在用于逻辑错误,而不是运行时错误。当您开始使用assert检查运行时错误时,您开始歪曲代码应该如何对尝试阅读它的任何人表现出来。维护者将无法轻易区分逻辑错误和潜在的运行时错误,并且在推理代码行为时这种区别很有用。

如果用户意外地将某些输入字段留空,您真的希望您的程序突然终止并转储核心吗?这是令人难以置信的用户敌意。

如果您觉得使用assert代码较少,那么您可以尝试编写自己的单独宏。这样可以避免改变assert的预期语义和敌意。例如,像:

#define FAIL_IF(cond, action) do { if (cond) { action; } } while (0)

FAIL_IF((fp = fopen(filename, "r")) == NULL, goto exit);

答案 7 :(得分:1)

我不同意那些说它有效的人的观点。一旦我被Qt的断言定义如下这一事实搞砸了:

#ifdef DEBUG
#  define q_assert(...) ...
#endif

我曾经写过像

这样的东西
q_assert(some_ptr_type a = get_new_ptr());

当我在发布模式下编写它时,a从未初始化时我感到非常困惑。

答案 8 :(得分:0)

与java异常类似assert提供故障快速。这是一个非常好的做法,可以节省大量的开发时间和精力。但是发货代码应该作为Web应用程序快速发布,而不是像CD发行版那样。此外,在java中,异常可能被截获,记录并很好地呈现。