DEBUG macros in C++

100,189

Solution 1

Is the second code snippet analogous to the one in 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 encounter mistakes due to a forgotten semicolon after the call. So I'd include this in a do block:

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

Do you have any favourite C++ debug macros?

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.

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.

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

Solution 2

Here's my favorite

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#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().

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)
#define DEBUG_STDOUT(x)
//... etc
#endif

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

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"); and proceeded to dump out a graphviz compatible 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!

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

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

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!

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

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.

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!

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.

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.

Solution 3

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

For question 2] There are many. 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.

Solution 4

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); 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 (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].

Solution 5

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...
Share:
100,189
Admin
Author by

Admin

Updated on July 05, 2022

Comments

  • Admin
    Admin almost 2 years

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

    #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 :-

    #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?

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

  • Tony Delroy
    Tony Delroy almost 11 years
    "My no-op usually just reads #define DEBUG(x)"... a do-while (false) substitution tends to produce an error if the DEBUG-invocation's semicolon is missing, instead of leaving the next statement to slip into the DEBUG's place. E.g. "if (expr) DEBUG", next line: "++i;", you'd get "if (expr) ++i;". Small reason to prefer do-while. A lot of the time putting "<< '\n'" into the macro is best too.
  • MvG
    MvG almost 11 years
    @TonyD: Good points, updated my answer. Although std::endl is superior to '\n' since it also flushes the stream.
  • Tony Delroy
    Tony Delroy almost 11 years
    in the case of std::cerr, it's normally line buffered so will flush anyway and using std::endl is cheaply redundant, but - pet peeve - what bothers me is people using std::endl in streaming operators that might be used for std::cout, a file stream etc - that screws with the buffering badly and can lead to dramatically worse performance.
  • user2672165
    user2672165 over 10 years
    Agree. However I find func somewhat more useful than FILE when func is available.
  • Jonathan Leffler
    Jonathan Leffler almost 9 years
    Observation: as currently shown, you always have the debugging enabled. You also limit the output to 100 characters (which isn't very long), but you don't ensure no buffer overflow by using snprintf() instead of sprintf(). That's living dangerously, isn't it? Shouldn't your logging go to std::cerr or std::clog? What is new and distinctly different about this answer compared to others?
  • Saurabh Shrivastava
    Saurabh Shrivastava almost 7 years
    Is it possible to include new line character (\n) in the macro itself?
  • Jack Wasey
    Jack Wasey almost 6 years
    The no-op used by assert is ((void)0)
  • cppBeginner
    cppBeginner over 5 years
    Wow, I have never known that macro parameter (x in D(x)) can also eat << and "". How far can it be interpreted? I heard that parameter of macro cannot contain character ( or ). May you provide some rule/limitation reference/link about it, please? It is hard to find such rule in books. Thank.
  • Steven Lu
    Steven Lu over 5 years
    AFAIK the way a macro works is it substitutes the string up until syntax prevents it from doing so. Because the parens are implicated in the substitution, you wouldn't be able to embed an unmatched set of parens, for example. And the commas are also implicated (in separating the macro args!). But everything else is fair game. Employ the tricks sparingly and responsibly...
  • Marcin Tarsier
    Marcin Tarsier over 4 years
    Don't ever use empty macros like # define D(x), they're very dangerous! as it has been pointed out in comments to the accepted answer (which also suffers from this issue) you get unexpected behaviour if you forget mistakenly semicolon afterwards like if (x) D(x) i++; where i++ is executed only if x is nonzero, while it looks as executed every time. If you put it like # define D(x) do { } while(0) it's still removed seamlessly by the compiler, but will yield compile error in case of missing semicolon.
  • Steven Lu
    Steven Lu over 4 years
    You're right @MarcinTarsier. I'll add it to my code listings to make them more safe. It's too bad that it's a bit uglier now.
  • Abhishek Avadhoot
    Abhishek Avadhoot over 2 years
    This wont work in a struct definition. struct { D(int proof_of_initialization;) ... }. After several attempts with debug on and off and moving the semicolon around I finally went with the simple and dangerous #define D(x) x and #define D(x). This has its problems as noted but on the bright side it actually works.
  • Steven Lu
    Steven Lu over 2 years
    Thanks @SamuelDanielson for pointing this out, it’s a bit embarrassing for me since I employ such usage in my examples. I will need to revert the latest edit I made to bring this answer back into a remotely correct state! In addition… this specific kind of do-while pattern maybe should have a if(0) { (void)x } or something inside, in order to surface x to the compiler to aid maintainability.
  • Steven Lu
    Steven Lu over 2 years
    @SamuelDanielson answer rolled back! Wonder if there exists a way to satisfy both semicolon safety and usability in struct definitions. I doubt it.