引言
我们在很多情况下需要用到assert,如当我们需要使用它去进行参数合法监测等,我们使用assert语句去实现,但是当软件发布时,因为性能问题等,会将assert去掉,所有的合法检测都失效了,我们又该如何监测参数的合法性呢?
如果我们将函数加上检测参数合法性的代码,使用if语言去判断,去处理,这就无疑增加了程序的开销,在什么样的场景下我们需要检查,也是一个问题。
下面我会分析我的解决方案。
如何进行参数检测?
程序处于开发阶段时
在这个时期,我们可以使用assert去进行函数参数合法性检查,以便迅速的查找到函数传参数的错误,方便调试。
程序发布时
在函数一般会通过一个宏取消assert,因为assert的输出信息没有意义了,而且没有assert可以提高效率。这时我们通常会使用if语句来判断是否合法,并进行不合法时的错误处理。
其中glib的解决方案是,使用是如下的宏函数:
g_return_val_if_fail(expr, value)
g_return_if_fail(expr)
// example
g_async_queue_length_unlocked (GAsyncQueue* queue)
{
g_return_val_if_fail (queue, 0);
g_return_val_if_fail (g_atomic_int_get (&queue->ref_count) > 0, 0);
return queue->queue->length - queue->waiting_threads;
}
g_return_val_if_fail的描述是当表达式expr为true时,直接返回value,g_return_val_if_fail是一个宏函数,所以它可以返回任何类型的值,当然,它要和函数返回值类型一致。return_if_fail就是直接返回了,适用于没有返回值的函数。
glib源代码中关于return_val_if_fail的实现,它比较复杂,但是主要就是当expr为true时,打印一条信息到某处,然后返回。我们可以简单的实现为如下的形式:
// user can disable parameters check by define NO_PARA_CHECK
#ifndef NO_PARA_CHECK
#define return_val_if_fail(expr, ret) \
do { \
if (!(expr)) { \
printf("%s:%d Warning: "#expr" failed.\n", \
__func__, __LINE__); \
return (ret); \
} \
} while (0);
#define return_if_fail(expr) \
do { \
if (!(expr)) { \
printf("%s:%d Warning: "#expr" failed.\n", \
__func__, __LINE__); \
return; \
} \
} while (0);
#else // ifndef NO_PARA_CHECK
#define return_val_if_fail(expr, ret)
#define return_if_fail(expr)
#endif // ifndef NO_PARA_CHECK
我们还可以利用宏来判断是否进行参数检测,正常运行的程序是不会出现参数不合法的情况,但是某些情况下,关于函数调用的参数是不太好预料的,使用参数检测能在错误的一开始就排查出来,能提高程序的可靠性。
总结一下,assert和return_val_if_fail的区别在于,assert会因为参数不合法终止程序,而return_val_if_fail不会,它会返回,将错误交给上层调用者来处理。有时候我们并不希望一个应该非法的参数而结束程序,而希望系统继续执行。如编写测试程序时,我测试多个模块,我们不希望一个模块的错误,而停止测试其他模块。
何时进行参数检测?
我们留到下篇分析。