如何确保程序按照你的预期运行
C语言编程中,经常由于各种想不到的原因导致程序崩溃,并且崩溃的时候没有详细的错误信息。很难定位到错误的位置。一个经常使用的方法是将代码中可能出错的地方,全部通过print打印出来,然后再运行程序,看看哪里出错了。
void
resetToZero(int i)
{
// 在这里修改i的值,使得i的值为0
while (i > 0) {
i--;
fprintf(stderr, "[DEBUG]: i = %d\n", i);
}
printf("i = %d\n", i);
}
运行程序可以通过打印的结果来对程序进行debug
$ gcc -o main main.c
$ ./main
[DEBUG]: i = 9
[DEBUG]: i = 8
[DEBUG]: i = 7
[DEBUG]: i = 6
[DEBUG]: i = 5
[DEBUG]: i = 4
[DEBUG]: i = 3
[DEBUG]: i = 2
[DEBUG]: i = 1
[DEBUG]: i = 0
i = 0
当定位到问题以后,修改原来的代码,删除print语句,完成修复。但是当程序规模变大以后,这种方法需要维护的print语句太多,而且每次修改完代码以后,还需要删除print语句,非常麻烦。如果你之前看过我的这篇C语言宏的文章,你可能会想到使用宏来解决这个问题。
// 定义一个宏,如果定义了NDEBUG,就什么都不做
#ifndef NDEBUG
#define debug_print(...) fprintf(stderr, __VA_ARGS__)
#else
#define debug_print(...)
#endif
void
resetToZero(int i)
{
// 在这里修改i的值,使得i的值为0
while (i > 0) {
i--;
debug_print("[DEBUG]: i = %d\n", i);
}
printf("i = %d\n", i);
}
$ gcc -o main main.c
$ ./main
[DEBUG]: i = 9
[DEBUG]: i = 8
[DEBUG]: i = 7
[DEBUG]: i = 6
[DEBUG]: i = 5
[DEBUG]: i = 4
[DEBUG]: i = 3
[DEBUG]: i = 2
[DEBUG]: i = 1
[DEBUG]: i = 0
i = 0
通过-DNDEBUG
选项,可以关闭debug_print语句
$ gcc -DNDEBUG -o main main.c
$ ./main
i = 0
通过上面这种方式,通过宏控制函数,避免了手动删除printf语句。
assert函数
在标准库中,有一个assert函数,通过#include <assert.h>
导入使用,在这个头文件中,assert其实是一个宏函数,他的定义与我们上面的定义类似。下面是一个简化后的assert的定义
#ifndef NDEBUG
#define assert(expr) \
((expr) ? (void)0 : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION))
#else
#define assert(expr) ((void)0)
#endif
assert的作用是,如果表达式expr为真,就什么都不做,如果表达式expr为假,就会打印出错误信息,并且终止程序的运行。将上面的函数修改为assert的形式,如下所示
void
resetToZero(int i)
{
// 在这里修改i的值,使得i的值为0
while (i > 0) {
i--;
}
// 确保i的值为1
assert(i == 1);
// 做其他的事情,比如打印i的值
printf("i = %d\n", i);
}
通过在关键的地方加入assert,可以当程序没有按照预期运行的时候,主动打印错误位置,然后终止程序的运行。这样就可以很方便的定位到错误的位置,而且不需要手动删除print语句。
$ gcc -o main main.c
$ ./main
main: main.c:13: resetToZero: Assertion `i == 1' failed.
[1] 82393 IOT instruction ./main
总结
在很多时候,程序不会报错,可以正常运行,但是程序运行的结果并不是你想要的结果,原因就是可能由于代码存在的bug,是的程序阴差阳错的运行了起来,可是其中的数据全部都是错误的,通过在一些关键位置添加上assert,在程序运行过程中,当数据与预期不符合的时候,就会终止程序的运行,这样就可以很方便的定位到错误的位置,然后修复bug。