优秀的编程知识分享平台

网站首页 > 技术文章 正文

C|从一个调试宏来了解错误处理、调试和宏的语法

nanyue 2024-09-01 20:38:48 技术文章 7 ℃

如果有一个函数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-

Tags:

最近发表
标签列表