按照字母序首先我们来看看<assert.h>,这个文件提供的接口功能很简单,但却是我们极其常用的功能-断言机制(如果条件为False,则输出Diagnostics信息,然后Abort)。当然在最终产品中使用断言并不是一种好的方法,不过断言是一种很有用的帮助我们调试程序的好工具。 我们一般在程序的调试版本中使用断言机制,一般用来对Input进行Validation,输出一些Diagnostics信息。如: assert((idx > 10) && (idx < 100)); <assert.h>中提供一个宏assert,这个宏的功能由另一个宏NDEBUG(标志是否是DEBUG版本)决定。如果NDEBUG宏在你include <assert.h>时没有被定义,这时断言功能开启;否则断言功能关闭。如: #define NDEBUG #include <assert.h> /* 此时断言功能关闭 */ 你也大可不必在你的各个源文件中控制断言功能的开关,在编译器选项中同样可以定义NDEBUG宏,如gcc -DNDEBUG test.c,当然对于大的project,这些是应该放在Makefile中的,这样的结果就相当于在你所有#include <assert.h>的地方之前定义了NDEBUG宏,也就是说在每个编译单元中,断言功能都是关闭的。 assert宏看起来很简单,但是由于其是C标准库提供的接口,所以在实现的时候需要考虑的更加细致和全面一些。从上面的叙述上来看assert.h文件的结构应该大致如下: #undef assert #ifdef NDEBUG #define assert(test) ((void)0) #else #define assert(test) ... #endif 我们可以很轻松的就拿出一个assert的实现版本: /* NDEBUG not defined */ #define assert(test) if (!(test)) \ fprintf(stderr, "Assertion Failed: %s, file %s, line %d\n", \ #test, __FILE__, __LINE__); \ 那么这个版本的实现可以接受不,答案是不能。原因有以下几点: 1) 这个实现中直接用到了stderr和fprintf,这两个符号都是在<stdio.h>中声明的,但是C标准库头文件基本上都是各自独立的,在<assert.h>中是不会再包含其他头文件的,那么这就要求使用assert的程序自己包含<stdio.h>,这显然不符合一个C标准库的基本要求; 2) assert宏应该最终展开为一个void expression,因为用户很可能在他们的程序中写出像(assert(0 < x), x < y)这样的代码来,而在上面的实现版本中,显然assert展开后不是一个void expression。 我们再来看看P.J.Plauger的实现版本: /* NDEBUG not defined */ void _Assert(char *); #define _STR(x) _VAL(x) #define _VAL(x) #x #define assert(test) (test) ? (void)0 \ : _Assert(__FILE__ ":" _STR(__LINE__) " " #test) /* in xassert.c */ #include <assert.h> #include <stdio.h> void _Assert(char *msg) { fprintf(stderr, "%s -- assertion failed\n", msg); abort(); } 分析一下这一版本的实现,首先assert宏并没有直接调用任何库输出函数,而是调用了一个自己实现的函数_Assert,把向stderr输出诊断信息的活都交给了_Assert。_STR和_VAL是两个辅助宏,用来将__LINE__字符串化。这里比较难懂的地方就是_Assert(__FILE__ ":" _STR(__LINE__) " " #test)这一句,其实这个也很好理解。看看下面语句的执行结果: printf("%s\n", "Hello" " " "Tony!"); 执行上面语句你会看到Hello Tony!,这样一来实际上_Assert(__FILE__ ":" _STR(__LINE__) " " #test)就可以被理解为: _Assert("THE_FILENAME_STRING" ":" "THE_LINE_STRING" " " "THE_TEST_STRING")
同意,只是存在这么一个宏,但是这个具体的值是不重要的,就可以把它忽略
|
|