当一个程序在运行时崩溃,那你最希望看到什么?他的错误信息,这样你就可以对其进行修复,所以一个在出错时毫无信息的程序那是很可怕的。因为所有人都不能保证他的程序100%的正确,即使程序完全正确,系统出错也很有可能导致程序相出错,所以,当程序出错时,应给出相应的提示信息,以便对其进行修复。
使用assert宏
assert宏定义在assert.h头文件中,其原型为:
#include<assert,h>
void assert(int expression);
他先计算表达式,若表达式的值为0,也就是假,将向srderr时输出一条信息,然后调用abort使程序停止运行,例如如下程序:
#include <assert.h>
#include <stdio.h>
int main(void)
{
FILE *f;
f = fopen("123", "r");
assert(f);
return 0;
}
除非你的目录下有123这个文件否则,就会出现下面的信息:
a.out: 1.c:8: main: Assertion `f' failed.
已放弃
另外,如果你使用 assert((f = fopen("123","r")));的话,那么当你,想用#define NDEBUG 来去掉assert的断言时,你就完了,因为(f = fopen("123","r")根本不会去执行,如果以后你还用这个文件的话,那错误就不在是,文件打开失败那么简单得了。所以,最好是将语句与条件分开来写。但是,大量使用assert断言,将会使你的程序变得很慢很慢,所以,不要将断言弄的满屏幕都是,除非你在作纯粹的测试是可以的。
还有两个宏很不错,__LINE__和__FILE__,他们能指出他们所在的行和所在的文件。我们将我们来打造一个更加安全的文件打开函数:
int fileopen(FILE ** f, const char * n, const char *m, const int l, const char *fn)
{
if (NULL == (*f = fopen(n,m)))
{
fprintf(stderr,"file:%s line : %d open file %s failed!\n",fn, l, n);
return 0;
}
return 1;
}
出错后的提示为:file:1.c line : 9 open file 123 failed!,很明显在1.c文件的第9行,打开文件出错。如果我们加上GNU C 扩展的__FUNCTION__宏,则还可以锁定具体的函数,例如,将上面的fileopen改为:
int fileopen(FILE ** f, const char * n, const char *m, const int l, const char *fn, const char * fun)
{
if (NULL == (*f = fopen(n,m)))
{
fprintf(stderr,"file:%s line : %d function :%s open file %s failed!\n",fn, l, fun, n);
return 0;
}
return 1;
}
调用时加上__FUNCTION__就行了,file:1.c line:9 functon: main open file 123 failed!这样,信息就会更加的详细一点,尤其是当调用关系很复杂的情况下,这个宏很管用。
另外,还有几个常用的函数来支持错误的处理,还有一个很用的变量来使用,现介绍一下配角,虽说配角,但只是在出错的时候。
void clearerr (FILE * stream); 清除EOF条件,以及任何为stream所设置的错误标志。
int feof (FILE * stream); 如果stream设置了EOF标志则返回真。
int ferror (FILE * stream); 如果设置了出错标志就返回真。
errno是一个变量,所有的函数都能对其操作,但没有那个库函数将其清零,所以,在使用的是后应现清零后使用。例如:
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
int main(void)
{
int i;
i =(int)sqrt((double)(-1));
if (errno)
perror("Wrong\n");
else
printf("%d",i);
return 0;
}
这是一个必然出错的程序,因为sqrt的取之不能为负数,(虽然数学上可以)由于我们使用了errno,所以其输出将会是:
Wrong
: Numerical argument out of domain
参数超出范围,这样的结果很明显,如果配合前面所说的几个宏来说,则会更好。另外值得要说的是,在连接math.h的时候要加上-l m参数;
变量errno的值
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Arg list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK 58 /* File locking deadlock error */
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
abort()函数
这个函数用起来很简单,只要abort()一下就可以了,但他作的更加简单,啥都不做,直接退出程序,告诉操作系统这是一个不正常的终止。如果没有ulimit的限制,abort还会卸下一个core文件。
exit()函数
相对于abort来说,exit的工作实在是太多了,和abort一样是退出程序,但exit会在完成清理工作之后才正式的退出,如果你用atexit注册了函数,用exit退出时,还将逆序调用这些注册函数,以进行程序的扫尾工作。
exit没有返回值,但他有一个参数,一个返回给OS 的退出值。理论上来说所有的整数都是合法的,但是标准库终止定义了EXIT_SUCCESS和EXIT_FAILURE两个用于退出的宏。而0是一个可移植的退出值。
atexit()函数
这个函数用来注册在程序退出时要完成的工作由哪些函数来作,其原型:
#include <stdlib.h>
int atexit(void(*function)(void));
如果函数注册成功,则返回0,否则返回1。
例如:
#include <stdio.h>
#include <stdlib.h>
void printexit(void);
int main(void)
{
if (atexit(printexit))
{
printf("Failed\n");
exit(EXIT_FAILURE);
}
printf("exiting...\n");
return 0;
}
void printexit(void)
{
printf("exit the program.\n");
}
整个程序并没有明显的调用printexit的地方,只有在atexit函数那里注册了一下,所以在程序return之后,就会执行printexit。
exiting...
exit the program.
strerror()函数
当错误返回是,并不是所有的人的都能看得懂错误的代码是什么,所以,就要将它转换成可读的字符串,strerror就是这个功能,我们只要将errno传给他就会返回对应的错误信息,而不在是简简单单的数字。
perror()函数
#include <stdio.h>
#include <errno.h>
void perror(const char *s);
他能够打印错误信息,并输出到stderr中。
利用系统日志进行出错处理
syslog的日志级别
#define LOG_EMERG 0 /* system is unusable */
#define LOG_ALERT 1 /* action must be taken immediately */
#define LOG_CRIT 2 /* critical conditions */
#define LOG_ERR 3 /* error conditions */
#define LOG_WARNING 4 /* warning conditions */
#define LOG_NOTICE 5 /* normal but significant condition */
#define LOG_INFO 6 /* informational */
#define LOG_DEBUG 7 /* debug-level messages */
功能值:
#define LOG_KERN (0<<3) /* kernel messages */
#define LOG_USER (1<<3) /* random user-level messages */
#define LOG_MAIL (2<<3) /* mail system */
#define LOG_DAEMON (3<<3) /* system daemons */
#define LOG_AUTH (4<<3) /* security/authorization messages */
#define LOG_SYSLOG (5<<3) /* messages generated internally by syslogd */
#define LOG_LPR (6<<3) /* line printer subsystem */
#define LOG_NEWS (7<<3) /* network news subsystem */
#define LOG_UUCP (8<<3) /* UUCP subsystem */
#define LOG_CRON (9<<3) /* clock daemon */
#define LOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */
#define LOG_FTP (11<<3) /* ftp daemon */
系统日志函数
#include <syslog.h>
void syslog (int priority, const char *format, ...);
其中,priority是级别与功能值相或的结果。format是写入的字符串,类似于printf的格式,%m表示,由strerror(errno)所生成的错误信息串。例如上面sqrt的错误,写入日志的话:
syslog(LOG_WARNING | LOG_USER, "%s %m",__FUNCTION__);
另外,由于LOG_USER是默认的级别,所以可以省略不写。
syslog(LOG_WARNING, "%s %m",__FUNCTION__);
使用openlog()来定制日志
#include <syslog.h>
void openlog (const char * ident, int option, int facility);
ident是加到日志消息前面的字符串,option是下表中0个或多个选项的或值,facility是级别值
#define LOG_PID 0x01 /* log the pid with each message */
#define LOG_CONS 0x02 /* log on the console if errors in sending */
#define LOG_ODELAY 0x04 /* delay open until first syslog() (default) */
#define LOG_NDELAY 0x08 /* don't delay open */
#define LOG_NOWAIT 0x10 /* don't wait for console forks: DEPRECATED */
#define LOG_PERROR 0x20 /* log to stderr as well */
如果你不调用他,syslog会在你第一次调用syslog函数的时候自动调用他,closelog用来关闭openlog打开的文件描述符。
void closelog (void);
例如:
openlog("my log", LOG_PID, LOG_USER);
syslog(LOG_NOTICE, "Nothing\n");
调用setlogmask为所有的日志消息设置默认级别
int setlogmask(int priority);
参数priority不是单个优先级就是优先级范围,函数设置了优先级的默认掩码,syslog拒绝任何没有在掩码中设置优先级的消息,为了简化优先级掩的设置,<syslog.h>定义了两个比较有用的宏。
LOG_MASK(int priority);
LOG_UPTO(int priority);
前者创建一个仅由一个优先级组成的掩码,priority作为参数传递,后者创建一个由一系列降序组成的掩码,这里的priority是允许的最低的优先级,例如,LOG_UPTO(LOG_NOTICE)创建的掩码包含从LOG_EMERG到LOG_NOTICE之间的任何级别的消息。而LOG_INFO LOG_DEBUG的消息不能通过。