[英]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
编辑:“调试宏”是指“在调试模式下运行程序时可能派上用场的宏”。
或多或少。 它更强大,因为您可以在参数中包含<<
-separated 值,因此使用单个参数,您将获得一些需要在 C 中使用可变数量的宏参数的东西。另一方面,人们的可能性很小将通过在参数中包含分号来滥用它。 甚至会因为调用后忘记分号而遇到错误。 所以我会将它包含在一个 do 块中:
#define DEBUG(x) do { std::cerr << x; } while (0)
我喜欢上面的那个并且经常使用它。 我的无操作通常只是阅读
#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()
)。 如果后者未定义,则它是一个调试版本。 简单! 所以,实际上没有理由再引入一个调试宏了! (并在未定义DEBUG
和NDEBUG
时处理可能的情况)。
这是我的版本,使用可变参数模板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 上,控制台输出很慢)。
您还可以使用临时stringstream
中print
,如果你只是想再次呼吁真正的输出功能(或写自己的类型安全printf
;-))
我使用下面的代码进行日志记录。 有几个优点:
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.