簡體   English   中英

C++ 中的調試宏

[英]DEBUG macros in C++

我剛剛在 C 中遇到了一個我非常喜歡的 DEBUG 宏

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

我猜 C++ 類似物是:-

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. 第二個代碼片段是否類似於 C 中的代碼片段?
  2. 你有什么最喜歡的 C++ 調試宏嗎?

編輯:“調試宏”是指“在調試模式下運行程序時可能派上用場的宏”。

第二個代碼片段是否類似於 C 中的代碼片段?

或多或少。 它更強大,因為您可以在參數中包含<< -separated 值,因此使用單個參數,您將獲得一些需要在 C 中使用可變數量的宏參數的東西。另一方面,人們的可能性很小將通過在參數中包含分號來濫用它。 甚至會因為調用后忘記分號而遇到錯誤。 所以我會將它包含在一個 do 塊中:

#define DEBUG(x) do { std::cerr << x; } while (0)

你有什么最喜歡的 C++ 調試宏嗎?

我喜歡上面的那個並且經常使用它。 我的無操作通常只是閱讀

#define DEBUG(x)

這對於優化編譯器具有相同的效果。 盡管下面@Tony D 的評論是正確的:這可能會導致一些語法錯誤未被發現。

我有時也會包含運行時檢查,從而提供某種形式的調試標志。 正如@Tony D 提醒我的那樣,在其中添加 endl 通常也很有用。

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

有時我也想打印表達式:

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

在一些宏中,我喜歡包含__FILE____LINE____func__ ,但這些更常見的是斷言而不是簡單的調試宏。

這是我最喜歡的

#ifdef DEBUG 
#define D(x) (x)
#else 
#define D(x) do{}while(0)
#endif

它非常方便,並且可以生成干凈的(重要的是,在發布模式下速度很快!!)代碼。

#ifdef DEBUG_BUILD都是#ifdef DEBUG_BUILD塊(過濾掉與調試相關的代碼塊)非常難看,但當你用D()包裹幾行時還不錯。

如何使用:

D(cerr << "oopsie";)

如果這對你來說仍然太丑陋/怪異/太長,

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x) do{}while(0)
#define DEBUG_STDOUT(x) do{}while(0)
//... etc
#endif

(我建議不要使用using namespace std;雖然可能using std::cout; using std::cerr;可能是個好主意)

請注意,您可能需要做更多的事情不僅僅是打印到標准錯誤,當你想“調試”。 發揮創意,您​​可以構建結構,深入了解程序中最復雜的交互,同時允許您非常快速地切換到構建不受調試工具阻礙的超高效版本。

例如,在我最近的一個項目中,我有一個巨大的僅用於調試的塊,它以FILE* file = fopen("debug_graph.dot"); 並繼續以點格式轉儲出一個graphviz兼容圖,以可視化我的數據結構中的大樹。 更酷的是 OS X graphviz 客戶端會在文件更改時自動從磁盤讀取文件,因此只要程序運行,圖形就會刷新!

我還特別喜歡使用僅用於調試的成員和函數來“擴展”類/結構。 這開辟了實現功能和狀態的可能性,這些功能和狀態可以幫助您跟蹤錯誤,並且就像調試宏中包含的其他所有內容一樣,通過切換構建參數來刪除。 一個巨大的例程,在每次狀態更新時都煞費苦心地檢查每個極端情況? 不是問題。 在它周圍打一個D() 一旦你看到它工作,從構建腳本中刪除-DDEBUG ,即為發布而構建,它就消失了,准備好在單元測試或你有什么的時候重新啟用。

一個大的,有點完整的例子,來說明(可能有點過分熱情)這個概念的使用:

#ifdef DEBUG
#  define D(x) (x)
#else
#  define D(x) do{}while(0)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) (x) // same concept as D(x) macro.
#  define N(x) do{}while(0)
#else
#  define U(x) do{}while(0)
#  define N(x) (x) // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

請注意,對於大塊代碼,我只使用常規塊#ifdef條件,因為這在一定程度上提高了可讀性,至於對於大塊,使用極短的宏更是一種障礙!

N(x)宏必須存在的原因是指定禁用單元測試時要添加的內容。

在這部分:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

如果我們能說類似的話就好了

GraphNode(uint64_t i, U(Component* c,) int first_edge):

但我們不能,因為逗號是預處理器語法的一部分。 省略逗號會產生無效的 C++ 語法。

如果您有一些額外的代碼用於編譯調試時,您可以使用這種類型的相應逆調試宏。

現在這段代碼可能不是“真正好的代碼”的一個例子,但它說明了一些你可以通過巧妙應用宏來完成的事情,如果你保持紀律,不一定是邪惡的。

在想知道do{} while(0)東西后,我剛剛偶然發現了這個 gem ,而且您確實也希望在這些宏中擁有所有這些幻想! 編輯更新:我繼續添加它們。

希望我的示例至少可以讓您深入了解一些可以用來改進 C++ 代碼的聰明方法。 在編寫代碼時對代碼進行檢測,而不是在不了解發生了什么時再回來編寫代碼,這真的很有價值。 但是,您必須在使其穩健和按時完成之間取得平衡。

我喜歡將額外的調試構建健全性檢查視為工具箱中的不同工具,類似於單元測試。 在我看來,它們可能更強大,因為與其將您的健全性檢查邏輯放在單元測試中並將它們與實現隔離,如果它們包含在實現中並且可以隨意調用,那么完整的測試就沒有必要了因為您可以簡單地啟用檢查並像往常一樣運行,在緊要關頭。

對於問題 1] 答案是肯定的。 它只會將消息打印到標准錯誤流。

對於問題 2] 有很多。 我最喜歡的是

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

這將允許在調試消息中包含任意數量的變量。

我喜歡用宏與__LINE____FILE__作為參數,以顯示代碼打印輸出是-它的情況並不少見在幾個地方進行打印相同的變量名,所以fprintf(stderr, "x=%d", x); 如果你再往下再添加一個相同的十行,就沒有多大意義了。

我還使用了覆蓋某些函數並存儲調用它的位置的宏(例如內存分配),以便稍后我可以找出泄漏的是哪個函數。 對於內存分配,這在 C++ 中有點困難,因為您傾向於使用 new/delete,並且它們不能輕易被替換,但是其他資源(例如鎖定/解鎖操作)對於以這種方式進行跟蹤非常有用[當然,如果您有一個像優秀的 C++ 程序員一樣使用構造/析構的鎖定包裝器,您可以將它添加到構造函數中,以便在獲得鎖后將文件/行添加到內部結構中,並且您可以看到它在其他地方的位置,當你不能在某處獲得它]。

這是我目前使用的日志宏:

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

用法:

log(">>> test...");

輸出:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...

......並作為所有回復的附錄:

就我個人而言,我從不使用像DEBUG這樣的宏來區分調試和發布代碼,而是使用NDEBUG ,它必須為發布版本定義以消除assert()調用(是的,我廣泛使用assert() )。 如果后者未定義,則它是一個調試版本。 簡單! 所以,實際上沒有理由再引入一個調試宏了! (並在未定義DEBUGNDEBUG時處理可能的情況)。

這是我的版本,使用可變參數模板print功能:

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

我使debug_print成為一個可變參數模板函數的版本,它接受一個調試級別,允許我選擇我想在運行時輸出的輸出類型:

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

請注意, print功能會導致 Visual Studio 2013 預覽print崩潰(我尚未測試 RC)。 我注意到它比我之前使用重載operator<<ostream子類的解決方案更快(在 Windows 上,控制台輸出很慢)。

您還可以使用臨時stringstreamprint ,如果你只是想再次呼吁真正的輸出功能(或寫自己的類型安全printf ;-))

我使用下面的代碼進行日志記錄。 有幾個優點:

  1. 我可以在運行時打開/關閉它們。
  2. 我可以在特定的日志級別編譯出語句。 例如,目前,我已經在KIMI_PRIVATE宏中無條件地編譯,因為我正在發布版本中調試某些東西,但是由於有很多潛在的秘密資料被記錄下來(lol),我從發布版本中編譯它.

多年來,這種模式對我很有幫助。 注意:雖然有一個全局的logMessage函數,但代碼通常會將日志logMessage日志線程。

#define KIMI_LOG_INTERNAL(level,EXPR)           \
  if(kimi::Logger::loggingEnabled(level))       \
  {                                             \
    std::ostringstream os;                      \
    os << EXPR;                                 \
    kimi::Logger::logMessage(level ,os.str());  \
  }                                             \
  else (void) 0

#define KIMI_LOG(THELEVEL,EXPR)                 \
  KIMI_LOG_INTERNAL(kimi::Logger::LEVEL_ ## THELEVEL,EXPR)

#define KIMI_ERROR(EXPR)   KIMI_LOG(ERROR,EXPR)
#define KIMI_VERBOSE(EXPR) KIMI_LOG(VERBOSE,EXPR)
#define KIMI_TRACE(EXPR)   KIMI_LOG(TRACE,EXPR)
#define KIMI_INFO(EXPR)    KIMI_LOG(INFO,EXPR)
#define KIMI_PROFILE(EXPR) KIMI_LOG(TRACE,EXPR)

// Use KIMI_PRIVATE for sensitive tracing
//#if defined(_DEBUG)
#  define KIMI_PRIVATE(EXPR) KIMI_LOG(PRIVATE,EXPR)
// #else
// #  define KIMI_PRIVATE(EXPR) (void)0
// #endif

我使用以下微,

#if DEBUG
#define LOGE2(x,y) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y <<std::endl;
#define LOGI2(x,y) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGD2(x,y) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGE(x) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGI(x) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGD(x) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#else
#define LOGE2(x,y) NULL
#define LOGI2(x,y) NULL
#define LOGD2(x,y) NULL
#define LOGE(x) NULL
#define LOGI(x) NULL
#define LOGD(x) NULL
#endif

采用:

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");

從其他答案中可以清楚地看出,有很多。 有一個我喜歡它,它允許可變數量的參數,打印參數的名稱,並且包括一個換行符。

void debug_print() { std::cerr << std::endl; }
template <typename Head, typename... Tail>
void debug_print(Head H, Tail... T) {
    std::cerr << ' ' << H;
    debug_print(T...);
}

#ifdef DEBUGFLAG
#  define DEBUG(...) std::cerr << "dbg  (" << #__VA_ARGS__ << "):", \
                     debug_print(__VA_ARGS__)
#else
#  define DEBUG(...) do {} while(0)
#endif

大部分解釋與此答案相同,但我發現當我想在一行上調試打印多個變量而沒有不同的宏時,附加模板可以簡化它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM