1. 除錯宏以及測試
在寫程式碼時,不可避免需要列印提示、警告、錯誤等資訊,且要靈活控制列印資訊的級別。另外,還有可能需要使用宏來控制程式碼段(主要是除錯程式碼段)是否執行。為此,本文提供一種除錯宏定義方案,包括列印字串資訊LOG1
宏和格式化列印LOG2
宏,且能透過宏控制程式碼段執行。完整程式碼如下:
#ifndef __DEBUG_H__
#define __DEBUG_H__
#include <iostream>
#include <string>
#include <stdio.h>
// 定義日誌級別列舉
enum LogLevel
{
DEBUG,
INFO,
WARN,
ERROR,
FATAL
};
// 全域性日誌級別變數宣告
extern LogLevel globalLogLevel;
// 定義日誌宏1
#define LOG1(level, message) do { \
if (level >= globalLogLevel) { \
std::cout << "[" #level "] " << __func__ << ":" << __LINE__ << " " << message << std::endl; \
} \
} while (0)
// 定義日誌宏2
// stdout帶緩衝,按行重新整理,fflush(stdout)強制重新整理
// stderr不帶緩衝,立刻重新整理到螢幕
#define LOG2(level, format, args...) do { \
if (level >= globalLogLevel) { \
fprintf(stderr, "[" #level "] %s:%d " format "\r\n", __func__, __LINE__, ##args); \
} \
} while (0)
// 透過宏控制除錯程式碼是否執行
#define EXECUTE
#ifdef EXECUTE
#define DEBUG_EXECUTE(code) {code}
#else
#define DEBUG_EXECUTE(code)
#endif
#endif
在main檔案進行宏定義測試,需要定義全域性日誌級別,以INFO
為例,則DEBUG
資訊不列印。測試檔案如下:
#include "debug.h"
// 全域性日誌級別變數定義
LogLevel globalLogLevel = INFO;
int main(void)
{
LOG1(DEBUG, "DEBUG message");
LOG1(INFO, "INFO message");
LOG1(WARN, "WARN message");
LOG1(ERROR, "ERROR message");
LOG1(FATAL, "FATAL message");
int num = 10;
LOG2(INFO, "num: %d", num);
DEBUG_EXECUTE(
LOG2(ERROR, "debug execute");
)
}
2. 宏定義小細節
2.1 #和##
兩者都是預處理運算子
- #是字串化運算子,將其後的宏引數轉換為用雙括號括起來的字串。
- ##是符號連線運算子,用於連線兩個標記(標記不一定是宏變數,可以是識別符號、關鍵字、數字、字串、運算子)為一個標記。
在第一章中使用#把日誌級別變數轉為字串,##的作用是在可變引數為0是,刪除前面的逗號,只輸出字串。
2.2 do while(0)
do while常用來做迴圈,而while引數為0,表示這樣的程式碼肯定不是做迴圈用的,它有什麼用呢?
- 輔助定義複雜宏,避免宏替換出錯
假如你定義一個這樣宏,本意是呼叫DOSOMETHING
時執行兩個函式。
#define DOSOMETHING() \
func1(); \
func2();
但在類似如下使用宏的程式碼,宏展開時func2
無視判斷條件都會執行。
if (0 < a)
DOSOMETHING();
// 宏展開後
if (0 < a)
func1();
func2();
最佳化一下,用{}
包裹宏是否可行呢?如下:
#define DOSOMETHING() { \
func1(); \
func2();}
由於我們寫程式碼習慣在語句後加分號,你可能會有如下的展開後編譯錯誤。
if(0 < a)
DOSOMETHING();
else
...
// 宏展開後
if(0 < a)
{
func1();
func2();
}; // 錯誤處
else
...
而do while (0)則能避免這些錯誤,所以複雜宏定義經常使用它。
- 消除分支語句或者goto語句,提高程式碼的易讀性
如果在一個函式中開始要分配一些資源,然後在中途執行過程中如果遇到錯誤則退出函式,當然,退出前先釋放資源,我們的程式碼可能是這樣:
bool Execute()
{
// 分配資源
int *p = new int;
bool bOk(true);
// 執行並進行錯誤處理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
// 執行成功,釋放資源並返回
delete p;
p = NULL;
return true;
}
這裡一個最大的問題就是程式碼的冗餘,而且我每增加一個操作,就需要做相應的錯誤處理,非常不靈活。於是我們想到了goto
:
bool Execute()
{
// 分配資源
int *p = new int;
bool bOk(true);
// 執行並進行錯誤處理
bOk = func1();
if(!bOk) goto errorhandle;
bOk = func2();
if(!bOk) goto errorhandle;
// 執行成功,釋放資源並返回
delete p;
p = NULL;
return true;
errorhandle:
delete p;
p = NULL;
return false;
}
程式碼冗餘是消除了,但是我們引入了C++
中身份比較微妙的goto
語句,雖然正確的使用goto
可以大大提高程式的靈活性與簡潔性,但太靈活的東西往往是很危險的,它會讓我們的程式捉摸不定,那麼怎麼才能避免使用goto
語句,又能消除程式碼冗餘呢,請看do...while(0)
:
bool Execute()
{
// 分配資源
int *p = new int;
bool bOk(true);
do
{
// 執行並進行錯誤處理
bOk = func1();
if(!bOk) break;
bOk = func2();
if(!bOk) break;
}while(0);
// 釋放資源
delete p;
p = NULL;
return bOk;
}
- 使用程式碼塊,程式碼塊內定義變數,不用考慮變數重複問題
顯而易見。
4. 參考博文
https://blog.csdn.net/keep_contact/article/details/127838298