且相应信号处理程序返回,abort()函数仍然终止程序。Posix.1也说明abort()函数并不理会进程对此信号的阻塞和忽略。
?
? ? ?进程捕捉到SIGABRT信号后,可在其终止之前执行所需的清理操作(如调用exit)。若进程不在信号处理程序中终止自己,Posix.1声明当信号处理程序返回时,abort()函数终止该进程。
?
? ? ?ISO C规定,abort()函数是否冲洗输出流、关闭已打开文件及删除临时文件由实现决定。Posix.1则要求若abort()函数终止进程,则它对所有打开标准I/O流的效果应当与进程终止前对每个流调用fclose相同。为提高可移植性,若希望冲洗标准I/O流,则应在调用abort()之前执行这种操作。
?
3.2 断言(assert)
? ? ?abort()和exit()函数无条件终止程序。也可使用断言(assert)有条件地终止程序。
?
? ? ?assert是诊断调试程序时经常使用的宏,定义在
内。该宏的典型实现如下:
?
复制代码
1 #ifdef ? ?NDEBUG
2 ? ? #define assert(expr) ? ? ? ?((void) 0)
3 #else
4 ? ? extern void __assert((const char *, const char *, int, const char *));
5 ? ? #define assert(expr) \
6 ? ? ? ? ((void) ((expr) || \
7 ? ? ? ? ?(__assert(#expr, __FILE__, __LINE__, __FUNCTION__), 0)))
8 #endif
复制代码
? ? ?可见,assert宏仅在Debug版本(定义NDEBUG)中有效,且调用__assert()函数。该函数将输出发生错误的文件名、代码行、函数名以及条件表达式:
?
复制代码
1 void __assert(const char *assertion, const char * filename,
2 ? ? ? ? ? ? ? int linenumber, register const char * function)
3 {
4 ? ? fprintf(stderr, " [%s(%d)%s] Assertion '%s' failed.\n",
5 ? ? ? ? ? ? filename, linenumber,
6 ? ? ? ? ? ? ((function == NULL) ? "UnknownFunc" : function),
7 ? ? ? ? ? ? assertion);
8 ? ? abort();
9 }
复制代码
? ? ?因此,assert宏实际上是一个带有错误说明信息的abort(),并做了前提条件检查。若检查失败(断言表达式为逻辑假),则报告错误并终止程序;否则继续执行后面的语句。
?
? ? ?使用者也可按需定制assert宏。例如,另一实现版本为:
?
复制代码
1 #undef assert
2 #ifdef NDEBUG
3 ? ? #define assert(expr) ? ? ? ?((void) 0)
4 #else
5 ? ? #define assert(expr) ? ? ? ?((void) ((expr) || \
6 ? ? ? ? ?(fprintf(stderr, "[%s(%d)] Assertion '%s' failed.\n", \
7 ? ? ? ? ?__FILE__, __LINE__, #expr), abort(), 0)))
8 #endif
复制代码
? ? ?注意,expr1||expr2表达式作为单独语句出现时,等效于条件语句if(!(expr1))expr2。这样,assert宏就可扩展为一个表达式,而不是一条语句。逗号表达式expr2返回最后一个表达式的值(即0),以符合||操作符的要求。
?
? ? ?使用断言时应注意以下几点:
?
? ? ?1) 断言用于检测理论上绝不应该出现的情况,如入参指针为空、除数为0等。
?
? ? ?对比以下两种情况:
?
复制代码
?1 char *Strcpy(char *pszDst, const char *pszSrc)?
?2 {?
?3 ? ? char *pszDstOrig = pszDst;?
?4 ? ? assert((pszDst != NULL) && (pszSrc != NULL));?
?5 ? ? while((*pszDst++ = *pszSrc++) != '\0');?
?6 ? ? ? ? return pszDstOrig;?
?7 }
?8 FILE *OpenFile(const char *pszName, const char *pszMode)
?9 {
10 ? ? FILE *pFile = fopen(pszName, pszMode);
11 ? ? assert(pFile != NULL);
12 ? ? if(NULL == pFile)
13 ? ? ? ? return NULL;
14?
15 ? ? //...
16 ? ? return pFile;
17 }
复制代码
? ? ?Strcpy()函数中断言使用正确,因为入参字符串指针不应为空。OpenFile()函数中则不能使用断言,因为用户可能需要检查某个文件是否存在,而这并非错误或异常。
?
? ? ?2)assert是宏不是函数,在调试版本和非调试版本中行为不同。因此必须确保断言表达式的求值不会产生副作用,如修改变量和改变方法的返回值。不过,可根据这一副作用测试断言是否打开:
?
复制代码
1 int main(void)
2 {
3 ? ? int dwChg = 0;
4 ? ? assert(dwChg = 1);
5 ? ? if(0 == dwChg)
6 ? ? ? ? printf("Assertion should be enabled!\n");
7 ? ? return 0;
8 }
复制代码
? ? ?3) 不应使用断言检查公共方法的参数(应使用参数校验代码),但可用于检查传递给私有方法的参数。
?
? ? ?4) 可使用断言测试方法执行的前置条件和后置条件,以及执行前后的不变性。
?
? ? ?5) 断言条件不成立时,会调用abort()函数终止程序,应用程序没有机会做清理工作(如关闭文件和
数据库)。?
?
3.3 封装
? ? ?为减少错误检查和处理代码的重复性,可对函数调用或错误输出进行封装。
?
? ? ?1) 封装具有错误返回值的函数
?
? ? ?通常针对频繁调用的基础性
系统函数,如内存和内核对象操作等。举例如下:
?
复制代码
?1 pid_t Fork(void) //首字母大写,以区分系统函数fork()
?2 {
?3 ? ? pid_t pid;
?4 ? ? if((pid = fork())<0)
?5 ? ? {
?6 ? ? ? ? fprintf(stderr, "Fork error: %s\n", strerror(errno));
?7 ? ? ? ? exit(0);
?8 ? ? }
?9 ? ? return pid;
10 }
复制代码
? ?