title: c的调试宏 date: 2023-08-18 20:52:55 tags: learn -18 C的调试宏
我在学习其他语言中看到许多语言对于错误处理有着不错的处理 但我在C语言中几乎没法看到类似的用法(可能是我的见识短浅) try catch等
但当我看到<<笨办法学C>>(learn C The Hard Way)中Zed的调试宏给了我非常大的启发
我用的环境是Linux + gcc 让我们来看看吧
Zed 在书中写到 这种机制可以检查现存的复杂代码中,你执行的东西是否发生错误。当你编写更多的C代码时,你应该按照下列模式: 1.调用函数。 2.如果返回值出现错误(每次都必须检查)。 3.清理创建的所有资源。 4.打印出所有可能有帮助的错误信息。
我认为一个函数一定要有一个检测 没有测试的代码一定是错的
zed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #ifndef __dbg_h__ #define __dbg_h__ #include <stdio.h> #include <errno.h> #include <string.h> #ifdef NDEBUG #define debug(M, ...) #else #define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__) #endif #define clean_errno() (errno == 0 ? "None" : strerror(errno)) #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__) #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; } #endif
让我来解读一下
1 2 3 4 5 #ifdef NDEBUG #define debug(M, ...) #else #define debug(M, ...) fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__) #endif
ifdef
如果定义
stderr
全称standard streams error
标准错误流与这个相似的stdin
,stdout
fprintf(stderr, "DEBUG %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__)
这是宏的主体部分,它使用fprintf
函数将信息输出到stderr
M
是传递给宏的第一个参数
__FILE__
当前文件名__LINE__
当前行号
##__VA_ARGS__
是一个特殊的宏,它将被替换为传递给debug宏的所有参数(除了M
)。
1 #define log_err(M, ...) fprintf(stderr, "[ERROR] (%s:%d: errno: %s) " M "\n", __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
clean_errno
宏用于获取errno的安全可读的版本
1 #define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; }
check
会保证条件A
为真,否则会记录错误M
(带着log_err
的可变参数),之后跳到函数的error:
区域来执行清理。 这是我见过最像try catch 用法的地方
看看用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 #include "dbg.h" #include <stdlib.h> #include <stdio.h> 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."); 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(); test_log_err(); test_log_warn(); test_log_info(); check(test_check("ex20.c") == 0, "failed with ex20.c"); check(test_check(argv[1]) == -1, "failed with argv"); check(test_sentinel(1) == 0, "test_sentinel failed."); check(test_sentinel(100) == -1, "test_sentinel failed."); check(test_check_mem() == -1, "test_check_mem failed."); check(test_check_debug() == -1, "test_check_debug failed."); return 0; error: return 1; }
输出
1 2 3 4 5 6 7 8 9 10 11 12 13 $ make ex20 cc -Wall -g -DNDEBUG ex20.c -o ex20 $ ./ex20 test [ERROR] (ex20.c:16: errno: None) I believe everything is broken. [ERROR] (ex20.c:17: errno: None) There are 0 problems in space. [WARN] (ex20.c:22: errno: None) You can safely ignore this. [WARN] (ex20.c:23: errno: None) Maybe consider looking at: /etc/passwd. [INFO] (ex20.c:28) Well I did something mundane. [INFO] (ex20.c:29) It happened 1.300000 times today. [ERROR] (ex20.c:38: errno: No such file or directory) Failed to open test. [INFO] (ex20.c:57) It worked. [ERROR] (ex20.c:60: errno: None) I shouldn't run. [ERROR] (ex20.c:74: errno: None) Out of memory.
真的是非常强大的zed的宏