'C|從一個調試宏來了解錯誤處理、調試和宏的語法'

GCC C語言 編譯器 小智雅匯 2019-08-05
"

如果有一個函數func(),定義返回0表示成功,出錯返回非0.

每次調用時,都要檢查這個錯誤代碼,通常會這樣寫:

int rc = func();
if(rc !=0)
{
\tfprintf(stderr,"There was an error:%s\\n",strerror());
\tgoto 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++等編譯器。

以下是一個完整的調試宏及測試代碼:

"

如果有一個函數func(),定義返回0表示成功,出錯返回非0.

每次調用時,都要檢查這個錯誤代碼,通常會這樣寫:

int rc = func();
if(rc !=0)
{
\tfprintf(stderr,"There was an error:%s\\n",strerror());
\tgoto 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++等編譯器。

以下是一個完整的調試宏及測試代碼:

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",\\
\t\t __FILE__, __LINE__, ##__VA_ARGS__)
\t\t //把...(多餘的參數)都放到##__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.");
\t//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);\t\t// 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:
\tDEBUG F:\\2C\\hardwayC\\ex19\\ex19.c:8: I have Brown Hair.
\tDEBUG F:\\2C\\hardwayC\\ex19\\ex19.c:11: I am 37 years old.
\t*/
\t
\t//test_log_err();
/*output:
[INFO] (F:\\ex19.c:28) Well I did something mundane.
\t[INFO] (F:\\ex19.c:29) It happened 1.300000 times today.
\t*/
\t
//test_log_warn();
/*output:
[INFO] (F:\\ex19.c:28) Well I did something mundane.
\t[INFO] (F:\\ex19.c:29) It happened 1.300000 times today.
\t*/
//check(test_check("ex20.c") == 0, "failed with ex20.c");
\t/*output:
\t[ERROR] (F:\\ex19.c:41: errno: No such file or directory) Failed
\t to open ex20.c.
\t[ERROR] (F:\\ex19.c:106: errno: None) failed with ex20.c
\t*/

//check(test_check(argv[1]) == -1, "failed with argv");
\t//output:[ERROR] (F:\\ex19.c:41: errno: No such file or directory)
\t//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.");
\t//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-

"

相關推薦

推薦中...