P.J.Plauger版本C标准库实现分析之'assert.h'
I believe that seeing a realistic implementation of the Standard C library can help you better understand how to use it.
— P.J.Plauger
按照字母序首先我们来看看<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")
评论