简体   繁体   English

C++ 中的调试宏

[英]DEBUG macros in C++

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?第二个代码片段是否类似于 C 中的代码片段?
  2. Do you have any favorite C++ debug macros?你有什么最喜欢的 C++ 调试宏吗?

EDIT : By "Debug Macros" I mean "macros that might come in handy while running a program in debug mode".编辑:“调试宏”是指“在调试模式下运行程序时可能派上用场的宏”。

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.它更强大,因为您可以在参数中包含<< -separated 值,因此使用单个参数,您将获得一些需要在 C 中使用可变数量的宏参数的东西。另一方面,人们的可能性很小将通过在参数中包含分号来滥用它。 Or even encounter mistakes due to a forgotten semicolon after the call.甚至会因为调用后忘记分号而遇到错误。 So I'd include this in a do block:所以我会将它包含在一个 do 块中:

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

Do you have any favourite 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 correct: 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__ ,但这些更常见的是断言而不是简单的调试宏。

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_BUILD blocks 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都是#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 suggest not 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 things than 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");例如,在我最近的一个项目中,我有一个巨大的仅用于调试的块,它以FILE* file = fopen("debug_graph.dot"); and proceeded to dump out a graphviz compatible graph in dot format to visualize large trees within my datastructures.并继续以点格式转储出一个graphviz兼容图,以可视化我的数据结构中的大树。 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!更酷的是 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.在它周围打一个D() Once you see it works, remove -DDEBUG from the build script, ie 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.一旦你看到它工作,从构建脚本中删除-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 #ifdef conditionals 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 add when 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 not compiling 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 necessarily evil.现在这段代码可能不是“真正好的代码”的一个例子,但它说明了一些你可以通过巧妙应用宏来完成的事情,如果你保持纪律,不一定是邪恶的。

I came across this gem just now after wondering about the do{} while(0) stuff, and you really do want all that fanciness in these macros as well!在想知道do{} while(0)东西后,我刚刚偶然发现了这个 gem ,而且您确实也希望在这些宏中拥有所有这些幻想! Edit update: I went ahead and added them.编辑更新:我继续添加它们。

Hopefully my example can provide some insight into at least some of the clever things that can be done to improve your C++ code.希望我的示例至少可以让您深入了解一些可以用来改进 C++ 代码的聪明方法。 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.但是,您必须在使其稳健和按时完成之间取得平衡。

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.在我看来,它们可能更强大,因为与其将您的健全性检查逻辑放在单元测试中并将它们与实现隔离,如果它们包含在实现中并且可以随意调用,那么完整的测试就没有必要了因为您可以简单地启用检查并像往常一样运行,在紧要关头。

For question 1] Answer is yes.对于问题 1] 答案是肯定的。 It will just print the message to standard error stream.它只会将消息打印到标准错误流。

For question 2] There are many.对于问题 2] 有很多。 My Fav is我最喜欢的是

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

which will allow one to include arbitrary number of variables to include in the debug message.这将允许在调试消息中包含任意数量的变量。

I like to use macros with __LINE__ , __FILE__ as arguments to show where in 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);我喜欢用宏与__LINE____FILE__作为参数,以显示代码打印输出是-它的情况并不少见在几个地方进行打印相同的变量名,所以fprintf(stderr, "x=%d", x); won't mean much if you then add another one the same ten lines further down.如果你再往下再添加一个相同的十行,就没有多大意义了。

I've also used macros that override certain functions and store where it was called from (eg 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++ 程序员一样使用构造/析构的锁定包装器,您可以将它添加到构造函数中,以便在获得锁后将文件/行添加到内部结构中,并且您可以看到它在其他地方的位置,当你不能在某处获得它]。

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...

… and as addendum to all responses: ......并作为所有回复的附录:

Personally I never use macros like DEBUG to distinct debug from release code, instead I use NDEBUG which is must be defined for release builds to eliminate assert() calls (yes, I use assert() extensively).就我个人而言,我从不使用像DEBUG这样的宏来区分调试和发布代码,而是使用NDEBUG ,它必须为发布版本定义以消除assert()调用(是的,我广泛使用assert() )。 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 DEBUG and NDEBUG both are not defined). (并在未定义DEBUGNDEBUG时处理可能的情况)。

This is my version, using a variadic template print function:这是我的版本,使用可变参数模板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_print a 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 print function crashes Visual Studio 2013 Preview (I haven't tested the RC).请注意, print功能会导致 Visual Studio 2013 预览print崩溃(我尚未测试 RC)。 I have noticed it is faster (on Windows, where console output is slow) than my previous solution which used an ostream child class that overloaded operator<< .我注意到它比我之前使用重载operator<<ostream子类的解决方案更快(在 Windows 上,控制台输出很慢)。

You can also use a temporary stringstream inside print if you only want to call the real output function once (or write your own typesafe printf ;-))您还可以使用临时stringstreamprint ,如果你只是想再次呼吁真正的输出功能(或写自己的类型安全printf ;-))

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_PRIVATE macro 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.例如,目前,我已经在KIMI_PRIVATE宏中无条件地编译,因为我正在发布版本中调试某些东西,但是由于有很多潜在的秘密资料被记录下来(lol),我从发布版本中编译它.

This pattern has served me very well over the years.多年来,这种模式对我很有帮助。 Note: although there is a global logMessage function, the code usually queues the log to a logging thread.注意:虽然有一个全局的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

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

As is clear from the other answers, there are many.从其他答案中可以清楚地看出,有很多。 There is one that I like which both allows for a variable number of arguments, prints the names of the arguments, and which includes a newline.有一个我喜欢它,它允许可变数量的参数,打印参数的名称,并且包括一个换行符。

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

Much of the explanation is as in this answer , but I find that the additional templating simplifies it when I want to debug-print several variables on one line without having different macros.大部分解释与此答案相同,但我发现当我想在一行上调试打印多个变量而没有不同的宏时,附加模板可以简化它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM