我发现使用assert(...)
使我的代码更短更容易阅读,而不是冗长的if..else..
块。但是,是否有良好的技术理由不在运费代码中使用assert(...)
,因为它与使用较少代码时测试return
值的做法相同?
答案 0 :(得分:4)
阅读this article后,我将分享我对assert
:
是的,当一些东西绝对符合你声称的条件时,可以使用assert
。
许多语言允许您在断言时引发自定义错误,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)
断言很好。编译时断言甚至更好。注意:
BOOST_STATIC_ASSERT()
。static_assert()
。 static_assert()
:Does GCC have a built-in compile time assert? 如果你的环境还没有静态断言,这是一个建议。
下面给出的ASSERT()
宏可以放在代码中的任何位置,除了:
#ifndef...#endif
包装。如果你想在结构定义的中间粘贴一些东西,你需要使用冗长,丑陋的三行构造#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中,异常可能被截获,记录并很好地呈现。