淺析在C/C++中如何寫調(diào)試宏
1. 調(diào)試宏以及測試
在寫代碼時,不可避免需要打印提示、警告、錯誤等信息,且要靈活控制打印信息的級別。另外,還有可能需要使用宏來控制代碼段(主要是調(diào)試代碼段)是否執(zhí)行。為此,本文提供一種調(diào)試宏定義方案,包括打印字符串信息LOG1宏和格式化打印LOG2宏,且能通過宏控制代碼段執(zhí)行。完整代碼如下:
#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)
// 通過宏控制調(diào)試代碼是否執(zhí)行
#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. 宏定義小細節(jié)
2.1 #和##
兩者都是預處理運算符
- #是字符串化運算符,將其后的宏參數(shù)轉(zhuǎn)換為用雙括號括起來的字符串。
- ##是符號連接運算符,用于連接兩個標記(標記不一定是宏變量,可以是標識符、關(guān)鍵字、數(shù)字、字符串、運算符)為一個標記。
在第一章中使用#把日志級別變量轉(zhuǎn)為字符串,##的作用是在可變參數(shù)為0是,刪除前面的逗號,只輸出字符串。
2.2 do while(0)
do while常用來做循環(huán),而while參數(shù)為0,表示這樣的代碼肯定不是做循環(huán)用的,它有什么用呢?
輔助定義復雜宏,避免宏替換出錯
假如你定義一個這樣宏,本意是調(diào)用DOSOMETHING時執(zhí)行兩個函數(shù)。
#define DOSOMETHING() \ func1(); \ func2();
但在類似如下使用宏的代碼,宏展開時func2無視判斷條件都會執(zhí)行。
if (0 < a)
DOSOMETHING();
// 宏展開后
if (0 < a)
func1();
func2();優(yōu)化一下,用{}包裹宏是否可行呢?如下:
#define DOSOMETHING() { \
func1(); \
func2();}由于我們寫代碼習慣在語句后加分號,你可能會有如下的展開后編譯錯誤。
if(0 < a)
DOSOMETHING();
else
...
// 宏展開后
if(0 < a)
{
func1();
func2();
}; // 錯誤處
else
...而do while (0)則能避免這些錯誤,所以復雜宏定義經(jīng)常使用它。
消除分支語句或者goto語句,提高代碼的易讀性
如果在一個函數(shù)中開始要分配一些資源,然后在中途執(zhí)行過程中如果遇到錯誤則退出函數(shù),當然,退出前先釋放資源,我們的代碼可能是這樣:
bool Execute()
{
// 分配資源
int *p = new int;
bool bOk(true);
// 執(zhí)行并進行錯誤處理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
}
// 執(zhí)行成功,釋放資源并返回
delete p;
p = NULL;
return true;
}這里一個最大的問題就是代碼的冗余,而且我每增加一個操作,就需要做相應的錯誤處理,非常不靈活。于是我們想到了goto:
bool Execute()
{
// 分配資源
int *p = new int;
bool bOk(true);
// 執(zhí)行并進行錯誤處理
bOk = func1();
if(!bOk) goto errorhandle;
bOk = func2();
if(!bOk) goto errorhandle;
// 執(zhí)行成功,釋放資源并返回
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
{
// 執(zhí)行并進行錯誤處理
bOk = func1();
if(!bOk) break;
bOk = func2();
if(!bOk) break;
}while(0);
// 釋放資源
delete p;
p = NULL;
return bOk;
}使用代碼塊,代碼塊內(nèi)定義變量,不用考慮變量重復問題
顯而易見。
到此這篇關(guān)于淺析在C/C++中如何寫調(diào)試宏 的文章就介紹到這了,更多相關(guān)C++寫調(diào)試宏 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

