如果有一个函数func(),定义返回0表示成功,出错返回非0.
每次调用时,都要检查这个错误代码,通常会这样写:
int rc = func(); if(rc !=0) { fprintf(stderr,"There was an error:%s\n",strerror()); goto error; }
如果定义了以下两个宏:
#define log_err(M, ...) fprintf(stderr,\ "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__,\ clean_errno(), ##__VA_ARGS__) #define check(A, M, ...) if(!(A)) {\ log_err(M, ##__VA_ARGS__); errno=0; goto error; }
函数调用时的错误检查代码就可以这样写:
int rc = func(); check(rc==0,"There was an error.");
以上定义的宏优势在于错误处理退出时可以显示行号、errno消息和进行goto错误处理操作业。另外,将if语句包裹在错误检查的宏中,可以清楚地表明是在做错误检查,而不是主流程的一部分。
宏虽然没有类型安全检查,但可以带参数封装任意表达式、语句块,可以嵌套,然后递归展开(函数也可以封装任意表达式、语句块,但不能嵌套定义,只能嵌套调用)
使用#define定义可变参数函数 是GCC 对ansi c的扩展,新的C99规范支持了可变参数的宏需要考虑使用GCC、Dev-C++等编译器。
以下是一个完整的调试宏及测试代码:
//dbg.h #ifndef __dbg_h__ #define __dbg_h__ #include <stdio.h> #include <errno.h> #include <string.h> #ifdef NDEBUG //可以定义一个NDEBUG宏来清理debug宏(调试日志) #define debug(M, ...)//注意右边为空 #else //以下M是参数替换,stderr是错误流,fprintf用得最多的是文件流 #define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n",\ __FILE__, __LINE__, ##__VA_ARGS__) //把...(多余的参数)都放到##__VA_ARGS__ #endif #define clean_errno() (errno == 0 ? "None" : strerror(errno)) //errno 0:Success //以下三个宏是用来记录给终端用户看的信息 #define log_err(M, ...) fprintf(stderr,\ "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__,\ clean_errno(), ##__VA_ARGS__) #define log_warn(M, ...) fprintf(stderr,\ "[WARN] (%s:%d: errno: %s) " M "\n",\ __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) #define log_info(M, ...) fprintf(stderr, "[INFO] (%s:%d) " M "\n",\ __FILE__, __LINE__, ##__VA_ARGS__) //A是一个条件表达式,为假时,显示错误信息,并跳到error:处去进行清理工作 #define check(A, M, ...) if(!(A)) {\ log_err(M, ##__VA_ARGS__); errno=0; goto error; } #define sentinel(M, ...) { log_err(M, ##__VA_ARGS__);\ errno=0; goto error; } #define check_mem(A) check((A), "Out of memory.") #define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__);\ errno=0; goto error; }//与check宏的区别是嵌套了debug而不是log_err宏 #endif //ex19.check#include "dbg.h" #include <stdlib.h> #include <stdio.h> //ex19.c编译后运行时要带一个任意参数 void test_debug() { // notice you don't need the \n debug("I have Brown Hair.");//看起来是一个函数,其实是一个宏 // passing in arguments like printf debug("I am %d years old.", 37); } void test_log_err() { log_err("I believe everything is broken."); log_err("There are %d problems in %s.", 0, "space"); } void test_log_warn() { log_warn("You can safely ignore this."); log_warn("Maybe consider looking at: %s.", "/etc/passwd"); } void test_log_info() { log_info("Well I did something mundane."); //mundane [m?n?de?n] adj.单调的; 平凡的; log_info("It happened %f times today.", 1.3f); } int test_check(char *file_name) { FILE *input = NULL; char *block = NULL; block = malloc(100); check_mem(block); // should work input = fopen(file_name, "r"); check(input, "Failed to open %s.", file_name); free(block); fclose(input); return 0; error: if (block) free(block); if (input) fclose(input); return -1; } int test_sentinel(int code) { char *temp = malloc(100); check_mem(temp); switch (code) { case 1: log_info("It worked."); break; default: sentinel("I shouldn't run."); } free(temp); return 0; error: if (temp) free(temp); return -1; } int test_check_mem() { char *test = NULL; check_mem(test); free(test); return 1; error: return -1; } int test_check_debug() { int i = 0; check_debug(i != 0, "Oops, I was 0."); return 0; error: return -1; } int main(int argc, char *argv[]) { check(argc == 2, "Need an argument."); test_debug(); /*output: DEBUG F:\2C\hardwayC\ex19\ex19.c:8: I have Brown Hair. DEBUG F:\2C\hardwayC\ex19\ex19.c:11: I am 37 years old. */ //test_log_err(); /*output: [INFO] (F:\ex19.c:28) Well I did something mundane. [INFO] (F:\ex19.c:29) It happened 1.300000 times today. */ //test_log_warn(); /*output: [INFO] (F:\ex19.c:28) Well I did something mundane. [INFO] (F:\ex19.c:29) It happened 1.300000 times today. */ //check(test_check("ex20.c") == 0, "failed with ex20.c"); /*output: [ERROR] (F:\ex19.c:41: errno: No such file or directory) Failed to open ex20.c. [ERROR] (F:\ex19.c:106: errno: None) failed with ex20.c */ //check(test_check(argv[1]) == -1, "failed with argv"); //output:[ERROR] (F:\ex19.c:41: errno: No such file or directory) //Failed to open test. //check(test_sentinel(1) == 0, "test_sentinel failed."); //output:[INFO] (F:\ex19.c:60) It worked. //check(test_sentinel(100) == -1, "test_sentinel failed."); //output:[ERROR] (F:\ex19.c:63: errno: None) I shouldn't run. //check(test_check_mem() == -1, "test_check_mem failed."); //output:[ERROR] (F:\ex19.c:78: errno: None) Out of memory. //check(test_check_debug() == -1, "test_check_debug failed."); //output:DEBUG F:\ex19.c:90: Oops, I was 0. return 0; error: return 1; }
参考:《笨办法”学C语言》
https://github.com/zedshaw/learn-c-the-hard-way-lectures/commit/4305a58bb9a8c516c0af4e9141126f2e52c18c6a
-End-