在C ++中debuggingmacros

我刚碰到一个我很喜欢的C语言的DEBUGmacros

#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 ++debuggingmacros吗?

编辑:“debuggingmacros”我的意思是“在debugging模式下运行程序时可能会派上用场的macros”。

第二个代码片段与C中的代码片段类似吗?

或多或less。 这是更强大的,因为你可以在参数中包含<<分离值,所以只需要一个参数就可以得到一些需要C中可变参数的参数。另一方面,人们将在论据中包含分号来滥用它。 甚至在通话之后由于忘记了分号而出现错误。 所以我会把它包含在一个do块中:

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

你有任何喜欢的C ++debuggingmacros吗?

我喜欢上面的那个,经常使用它。 我的无操作通常只是读取

 #define DEBUG(x) 

这对优化编译器具有相同的效果。 虽然下面的@Tony D的评论是相同的:这可能会遗漏一些语法错误。

我有时也包括一个运行时检查,从而提供某种forms的debugging标志。 正如@Tony D提醒我的那样,在那里有一个endl也经常是有用的。

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

有时我也想打印expression式:

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

在一些macros中,我喜欢包含__func__ __FILE____func____func__ ,但是这些更多的是断言而不是简单的debuggingmacros。

这是我最喜欢的

 #ifdef DEBUG #define D(x) x #else #define D(x) #endif 

这是超级方便,使得清洁(重要的是,快速释放模式!!)的代码。

许多#ifdef DEBUG_BUILD块遍布整个地方(过滤debugging相关的代码块)是非常丑陋的,但是用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) #define DEBUG_STDOUT(x) //... etc #endif 

(我build议不要使用using namespace std;虽然也许using std::cout; using std::cerr;可能是一个好主意)

请注意,当您考虑“debugging”时,您可能需要做更多的事情,而不仅仅是打印到stderr。 获得创意,您可以构build能够深入了解程序中最复杂的交互的结构,同时允许您快速切换到构build一个不受debugging工具影响的超高效版本。

例如,在我最近的项目中,我有一个巨大的debugging块,它以FILE* file = fopen("debug_graph.dot"); 并继续以点格式转储出graphviz兼容的graphics,以可视化我的数据结构中的大型树。 更酷的是,OS X graphviz客户端将在磁盘更改时自动从磁盘读取文件,因此只要程序运行,graphics就会刷新!

我还特别喜欢用“只debugging”成员和函数“扩展”类/结构。 这就开辟了实现function和状态的可能性,帮助你跟踪错误,就像在debuggingmacros中包装的其他东西一样,通过切换构build参数来移除它。 一个巨大的例程,在每个状态更新中刻意检查每个angular落案例? 不是问题。 打一个D()在它周围。 一旦你看到它的工作,从构build脚本中删除-DDEBUG ,即build立发布,它已经消失了,准备好在你的unit testing的通知你什么时候重新启用。

一个很大的,有点完整的例子,来说明(或许有些过分热心)这个概念的使用:

 #ifdef DEBUG # define D(x) x #else # define D(x) #endif // DEBUG #ifdef UNITTEST # include <UnitTest++/UnitTest++.h> # define U(x) x // same concept as D(x) macro. # define N(x) #else # define U(x) # 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条件,因为它提高了可读性,对于大块使用非常短的macros更是一个障碍!

N(x)macros必须存在的原因是指定禁用unit testing时要添加的内容。

在这个部分:

 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 ++语法。

如果你有一些额外的代码,当编译debugging,你可以使用这种types的相应的反debuggingmacros。

现在这个代码可能不是“真正的好代码”的例子,但是它展示了一些你可以用聪明的macros应用程序来完成的事情,如果你遵守纪律的话,它们并不一定是邪恶的。

刚才我想到do{} while(0)东西,碰到了这个gem ,而且你真的也想要这些macros的所有fanciness!

希望我的例子可以提供一些洞察,至less可以做一些聪明的事情来改善你的C ++代码。 在写代码的时候代码是非常有价值的,而不是在你不明白发生了什么事情的时候回头去做。 但是总要保持一个平衡点,就是要把它做好,按时完成。

我喜欢用__LINE__ __FILE____FILE__作为参数来显示打印输出在代码中的位置 – 在几处打印相同的variables名并不罕见,所以fprintf(stderr, "x=%d", x); 如果你再添加一个相同的十行的话,这并不意味着太多。

我也使用了重写某些函数的macros,并将其存储在被调用的地方(例如内存分配),以便以后可以找出泄漏的是哪一个。 对于内存分配来说,在C ++中有点难度,因为你倾向于使用new / delete,并且它们不能被轻易地replace,但是其他资源,比如locking/解锁操作,对于追踪这种方式是非常有用的[当然,如果你有一个像使用C ++程序员那样使用构造/破坏的locking包装,你可以将它添加到构造函数中,一旦你获得了locking,就可以将文件/行添加到内部结构中,你不能在某处获得]。

对于问题1]答案是肯定的。 它只会将消息打印到标准错误stream。

对于问题2]有很多。 我的collections是

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

这将允许在debugging消息中包含任意数量的variables。

这是我目前使用的日志macros:

 #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这样的macros来从发布代码中区分出debugging,而是使用必须为发布版本定义的 NDEBUG来消除assert()调用(是的,我广泛地使用了assert() )。 如果后者没有定义,那么这是一个debugging版本。 简单! 所以,实际上没有理由再引入一个debuggingmacros! (并且当DEBUGNDEBUG都没有定义的时候处理可能的情况)。

我使用下面的代码进行日志logging。 有几个优点:

  1. 我可以在运行时打开/closures它们。
  2. 我可以在特定的日志级别编译语句。 例如,目前,我已经无条件地编译了KIMI_PRIVATEmacros,因为我在发布版本中debugging了一些东西,但是由于有很多潜在的秘密东西被logging(lol),所以我把它从发布版本编译。

这种模式多年来一直很好。 注意:尽pipe有一个全局的logMessage函数,但代码通常会将日志排队到一个日志logging线程。

 #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 

这是我的版本,使用可变模板printfunction:

 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成为一个可变模板函数,该函数接受一个debugging级别,允许我select在运行时输出什么types的输出:

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

请注意printfunction崩溃Visual Studio 2013预览(我还没有testingRC)。 我注意到它比我以前使用重载operator<<ostream子类的解决scheme更快(在Windows上,控制台输出缓慢)。

如果你只想调用一次真正的输出函数(或者编写你自己的types安全的printf ;-)),你也可以在print使用一个临时的stringstream

我使用以下微型,

 #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");