C++ 中的调试宏

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14251038/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 18:09:50  来源:igfitidea点击:

DEBUG macros in C++

c++cdebuggingc-preprocessor

提问by

I just encountered a DEBUG macro in C that I really like

我刚刚在 C 中遇到了一个我非常喜欢的 DEBUG 宏

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

I'm guessing a C++ analogue would be :-

我猜 C++ 模拟将是:-

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. Is the second code snippet analogous to the one in C?
  2. Do you have any favorite C++ debug macros?
  1. 第二个代码片段是否类似于 C 中的代码片段?
  2. 你有什么最喜欢的 C++ 调试宏吗?

EDIT : By "Debug Macros" I mean "macros that might come in handy while running a program in debug mode".

编辑:“调试宏”是指“在调试模式下运行程序时可能会派上用场的宏”。

采纳答案by MvG

Is the second code snippet analogous to the one in C?

第二个代码片段是否类似于 C 中的代码片段?

More or less. It's is more powerful, as you can include <<-separated values in the argument, so with a single argument you get something that would require a variable number of macro arguments in C. On the other hand, there is a slim chance that people will abuse it by including a semicolon in the argument. Or even encouter mistakes due to a forgotten semicolon after the call. So I'd include this in a do block:

或多或少。它更强大,因为您可以<<在参数中包含-separated 值,因此使用单个参数您会得到一些需要在 C 中使用可变数量的宏参数的东西。另一方面,人们滥用的可能性很小它通过在参数中包含一个分号。甚至在调用后由于忘记分号而遇到错误。所以我会将它包含在一个 do 块中:

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

Do you have any favorite C++ debug macros?

你有什么最喜欢的 C++ 调试宏吗?

I like the one above, and use it quite often. My no-op usually just reads

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

#define DEBUG(x)

which has the same effect for optimizing compilers. Although the comment by @Tony D below is corret: this can leave some syntax errors undetected.

这对于优化编译器具有相同的效果。尽管下面@Tony D 的评论是正确的:这可能会导致一些语法错误未被发现。

I sometimes include a run-time check as well, thus providing some form of a debug flag. As @Tony D reminded me, having an endl in there is often useful as well.

我有时也会包含运行时检查,从而提供某种形式的调试标志。正如@Tony D 提醒我的那样,在其中添加 endl 通常也很有用。

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

Sometimes I also want to print the expression:

有时我也想打印表达式:

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

In some macros, I like to include __FILE__, __LINE__or __func__, but these are more often assertions and not simple debug macros.

在某些宏中,我喜欢包含__FILE__,__LINE____func__,但这些更常见的是断言而不是简单的调试宏。

回答by Steven Lu

Here's my favorite

这是我最喜欢的

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

It's super handy and makes for clean (and importantly, fast in release mode!!) code.

它非常方便,并且可以生成干净的(重要的是,在发布模式下速度很快!!)代码。

Lots of #ifdef DEBUG_BUILDblocks all over the place (to filter out debug related blocks of code) is pretty ugly, but not so bad when you wrap a few lines with a D().

#ifdef DEBUG_BUILD到处都是很多块(过滤掉与调试相关的代码块)非常难看,但当你用D().

How to use:

如何使用:

D(cerr << "oopsie";)

If that's still too ugly/weird/long for you,

如果这对你来说仍然太丑陋/怪异/太长,

#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

(I suggestnot using using namespace std;though maybe using std::cout; using std::cerr;could be a good idea)

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

Note that you might want to do more thingsthan just print to stderr when you are thinking about "debugging". Get creative, and you can build constructs that offer insight into the most complex interactions within your program, while allowing you to very quickly switch to building a super efficient version unencumbered by debug instrumentation.

请注意,你可能想要做更多的事情不仅仅是打印到标准错误,当你想“调试”。发挥创意,您​​可以构建结构,深入了解程序中最复杂的交互,同时允许您非常快速地切换到构建不受调试工具阻碍的超高效版本。

For example in one of my recent projects I had a huge debug-only block which started with FILE* file = fopen("debug_graph.dot");and proceeded to dump out a graphvizcompatible graph in dot format to visualize large trees within my datastructures. What's even cooler is the OS X graphviz client will auto-read the file from disk when it changes, so the graph refreshes whenever the program is run!

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

I also particularly like to "extend" classes/structs with debug-only members and functions. This opens up the possibility of implementing functionality and state that is there to help you track down bugs, and just like everything else that is wrapped in debug macros, is removed by switching a build parameter. A giant routine that painstakingly checks each corner case on every state update? Not a problem. Slap a D()around it. Once you see it works, remove -DDEBUGfrom the build script, i.e. build for release, and it's gone, ready to be re-enabled at a moment's notice for your unit-testing or what have you.

我还特别喜欢使用仅用于调试的成员和函数来“扩展”类/结构。这开辟了实现功能和状态的可能性,这些功能和状态可以帮助您跟踪错误,并且就像调试宏中包含的其他所有内容一样,通过切换构建参数来删除。一个巨大的例程,在每次状态更新时都煞费苦心地检查每个极端情况?不是问题。D()在它周围打一个。一旦你看到它工作,-DDEBUG从构建脚本中删除,即为发布而构建,它就消失了,准备在单元测试或你有什么的时候重新启用。

A large, somewhat complete example, to illustrate (a perhaps somewhat overzealous) use of this concept:

一个大的,有点完整的例子,来说明(可能有点过分热情)这个概念的使用:

#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

Notice that for large blocks of code, I just use regular block #ifdefconditionals because that improves readability somewhat, as for large blocks the use of extremely short macros is more of a hindrance!

请注意,对于大块代码,我只使用常规块#ifdef条件,因为这在一定程度上提高了可读性,至于对于大块,使用极短的宏更像是一个障碍!

The reason why the N(x)macro must exist is to specify what to addwhen unit-testing is disabled.

N(x)宏必须存在的原因是指定禁用单元测试时要添加的内容

In this part:

在这部分:

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

It would be nice if we could say something like

如果我们能说类似的话就好了

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

But we cannot, because the comma is a part of preprocessor syntax. Omitting the comma produces invalid C++ syntax.

但我们不能,因为逗号是预处理器语法的一部分。省略逗号会产生无效的 C++ 语法。

If you had some additional code for when notcompiling for debug, you could use this type of corresponding inverse-debug macro.

如果您有一些额外的代码用于编译调试时,您可以使用这种类型的相应逆调试宏。

Now this code might not be an example of "really good code" but it illustrates some of the things that you can accomplish with clever application of macros, which if you remain disciplined about, are not necessarilyevil.

现在这段代码可能不是“真正好的代码”的一个例子,但它说明了一些你可以通过巧妙应用宏来完成的事情,如果你保持纪律,不一定是邪恶的。

I came across this gemjust now after wondering about the do{} while(0)stuff, and you really do want all that fanciness in these macros as well! Edit update:I went ahead and added them.

我刚刚在想知道这些东西后偶然发现了这个宝石do{} while(0),而且您确实也想要这些宏中的所有奇特之处!编辑更新:我继续添加它们。

Hopefully my example can provide some insight into at least some of the clever things that can be done to improve your C++ code. It is really valuable to instrument code while you write it rather than to come back to do it when you don't understand what's happening. But it is always a balance that you must strike between making it robust and getting it done on time.

希望我的示例至少可以让您深入了解一些可以用来改进 C++ 代码的聪明方法。在编写代码时对代码进行检测,而不是在不了解发生了什么时再回来编写代码,这真的很有价值。但是,您必须在使其稳健和按时完成之间取得平衡。

I like to think of additional debug build sanity checks as a different tool in the toolbox, similar to unit tests. In my opinion, they could be even more powerful, because rather than putting your sanity check logic in unit tests and isolating them from the implementation, if they are included in the implementation and can be conjured at will, then complete tests are not as necessary because you can simply enable the checks and run things as usual, in a pinch.

我喜欢将额外的调试构建健全性检查视为工具箱中的不同工具,类似于单元测试。在我看来,它们可能更强大,因为与其将您的健全性检查逻辑放在单元测试中并将它们与实现隔离,如果它们包含在实现中并且可以随意调用,那么完整的测试就没有必要了因为您可以简单地启用检查并像往常一样运行,在紧要关头。

回答by NeonGlow

For question 1] Answer is yes. It will just print the message to standard error stream.

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

For question 2] There are many. My Fav is

对于问题 2] 有很多。我最喜欢的是

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

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

which will allow one to include arbitrary number of variables to include in the debug message.

这将允许在调试消息中包含任意数量的变量。

回答by Mats Petersson

I like to use macros with __LINE__, __FILE__as arguments to show wherein the code the printout is from - it's not uncommon to print the same variable name in several places, so fprintf(stderr, "x=%d", x);won't mean much if you then add another one the same ten lines further down.

我喜欢使用带有__LINE__, 的宏__FILE__作为参数来显示打印输出来自代码中的哪个位置- 在多个位置打印相同的变量名称并不少见,因此fprintf(stderr, "x=%d", x);如果您再添加另一个相同的十行,则意义不大下。

I've also used macros that override certain functions and store where it was called from (e.g. memory allocations), so that later on, I can figure out which one it was that leaked. For memory allocation, that's a little harder in C++, since you tend to use new/delete, and they can't easily be replaced, but other resources such as lock/unlock operations can be very useful to trace this way [of course, if you have a locking wrapper that uses construction/destruction like a good C++ programmer, you'd add it to the constructor to add file/line to the internal structure once you have acquired the lock, and you can see where it's held elsewhere when the you can't acquire it somewhere].

我还使用了覆盖某些函数并存储调用它的位置的宏(例如内存分配),以便稍后我可以找出泄漏的是哪个函数。对于内存分配,这在 C++ 中有点困难,因为您倾向于使用 new/delete,并且它们不能轻易被替换,但是其他资源(例如锁定/解锁操作)对于以这种方式进行跟踪非常有用[当然,如果您有一个像优秀的 C++ 程序员一样使用构造/析构的锁定包装器,您可以将它添加到构造函数中,以便在获得锁后将文件/行添加到内部结构中,并且您可以看到它在其他地方的位置你不能在某处获得它]。

回答by firestoke

This is the log macro I am using currently:

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

#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

Usage:

用法:

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

Output:

输出:

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

回答by zaufi

… and as addendum to all responses:

......并作为所有回复的附录:

Personally I never use macros like DEBUGto distinct debug from release code, instead I use NDEBUGwhich is must be definedfor release builds to eliminate assert()calls (yes, I use assert()extensively). And if latter is not defined, then it is a debug build. Easy! So, actually there is no reason to introduce one more debug macro! (and handle possible cases when DEBUGand NDEBUGboth are not defined).

我个人从来没有使用宏象DEBUG以不同的调试从发布代码,而不是我用NDEBUG必须定义为发布版本,以消除assert()电话(是的,我使用assert()广泛)。如果后者未定义,则它是一个调试版本。简单!所以,实际上没有理由再引入一个调试宏了!(并在DEBUGNDEBUG两者都未定义时处理可能的情况)。

回答by rubenvb

This is my version, using a variadic template printfunction:

这是我的版本,使用可变参数模板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

The version I makes the debug_printa variadic template function which accepts a debug level which allows me to select what kind of output I want to output at runtime:

我制作了debug_print一个可变参数模板函数的版本,它接受一个调试级别,允许我选择我想在运行时输出的输出类型:

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

Note the printfunction crashes Visual Studio 2013 Preview (I haven't tested the RC). I have noticed it is faster (on Windows, where console output is slow) than my previous solution which used an ostreamchild class that overloaded operator<<.

请注意,该print函数会导致 Visual Studio 2013 预览版崩溃(我尚未测试 RC)。我注意到它比我以前使用ostream重载operator<<.

You can also use a temporary stringstreaminside printif you only want to call the real output function once (or write your own typesafe printf;-))

如果您只想调用一次真正的输出函数(或编写自己的类型安全;-)),您也可以使用临时stringstream内部printprintf

回答by cheez

I use the code below for logging. There are a few advantages:

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

  1. I can turn them on/off at runtime.
  2. I can compile out statements at a particular log level. For example, at the moment, I've unconditionally compiled in the KIMI_PRIVATEmacro because I'm debugging something in the release build but since there is a lot of potentially secret sauce stuff being logged (lol), I compile it out of release builds.
  1. 我可以在运行时打开/关闭它们。
  2. 我可以在特定的日志级别编译出语句。例如,目前,我已经无条件地在KIMI_PRIVATE宏中编译,因为我正在发布版本中调试某些东西,但是由于有很多潜在的秘密资料被记录下来(lol),我从发布版本中编译它。

This pattern has served me very well over the years. Note: although there is a global logMessagefunction, the code usually queues the log to a logging thread.

多年来,这种模式对我很有帮助。注意:虽然有一个全局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

回答by nilesh suryawanshi

I use following micro,

我使用以下微,

#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

USE:

用:

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