Tuesday, August 14, 2012

Debugging C++ (Part 4): A print method

And here is the last post of this series about debugging C++. In this one, I develop a good way to use prints for debugging. The main drawback of the prints is that it is hard to maintain. The presented method doesn't have this problem.

I use prints sometimes, when valgrind tells me nothing, and that I can't run gdb for any reason (for example, I use opaque data structure and all the information gdb is able to give is "root node = 2"… Nice! It happens when I work with BDD (Binary Decision Diagram, I think I'll talk about it in the future)). This method works in C++, and is inspired of three things. I'll start by describing these things, and then I show the whole thing.

Akim Demaille, one of my teacher, gave me the base of this method. It consists in using macro to print. Here is the thing:

#define ECHO(content) std::cerr << content << std::endl
#define VAR(v) "`" #v "': " << v

int main(int argc, char *argv[])
{
  int i = 42;
  int j = 51;

  ECHO(VAR(i) << " - " << VAR(j)); // Outputs "`i': 42 - `j': 51"
}

I add quote _`'_ to see what happens when I want to see the result of an operation (i + j for example). Now, let's see how it works. In C or C++, you can make a concatenation by just putting two quoted things side by side. As an example this snippets

std::cout << "42 " "is " "the " "answer." << std::endl;

outputs "42 is the answer.". These method also relies on the stringification. The operator # put in front of a variable in a macro quotes it. It allows to make this. The macro VAR takes a variable, and returns its name quoted, and its value ready for being printed to std::cerr. And ECHO just print it followed by a newline.

Looks simple when you see it, but I found it ingenious. I remember myself making this job by hand so much time… I think this was very cool and a sane base to build something better!

We had a discussion with a friend of mine (Guillaume Delahodde) about a way to decide whether print a thing or not when we want to debug or not without any run-time overhead. And the result of this discussion was a macro that decides to execute the code or not:

#ifndef NDEBUG
# define LINE(content) content
#else
# define LINE(content)
#endif

I already talk about NDEBUG in the first post of this series about simple methods to avoid creating stupid bug, so I assume it is clear why it appears here. The usage of this is simple, put your print code inside LINE and it will be printed only in debug mode. So we just have to change the definition of ECHO a little, and it will only print on debug mode.

#define ECHO(content) LINE(std::cerr << content << std::endl)

Now our prints are only here in debug mode, and it is better than before. We could have stop here, but we could enhance the comfort of the user. I think writing VAR(i) << " - " << VAR(j) very annoying. It corresponds to 25 characters, and I think this is too much. I had the occasion to read the book of Andrei Alexandrescu (Modern C++ Design), and in there it uses a set of macros to add syntactic sugar for the user. It is hand written macros that threat the first argument and call the macro that threats n-1 arguments until 1. I call this macro LVN with N the number of argument. I just wrote it for 2, 3 and 4 arguments since they are the more common. Let's see how it looks like:

#define LV2(first, second) VAR(first) << " - " << VAR(second)
#define LV3(first, second, third) VAR(first) << " - " << LV2(second, third)
#define LV4(first, second, third, fourth) \
  VAR(first) << " - " << LV3(second, third, fourth)

And now we are able to replace our 25 characters to print 2 variables into: LV2(i, j). Much better right? Let's see the whole thing in an example:

#include <iostream>

#ifndef NDEBUG
# define LINE(line) line
#else
# define LINE(line)
#endif

#define ECHO(content) LINE(std::cerr << content << std::endl)
#define VAR(v) "`" #v "': " << v

#define LV2(first, second) VAR(first) << " - " << VAR(second)
#define LV3(first, second, third) VAR(first) << " - " << LV2(second, third)
#define LV4(first, second, third, fourth) \
  VAR(first) << " - " << LV3(second, third, fourth)

int main(int argc, char *argv[])
{
  int i = 4;
  int j = 51;

  std::cerr << "`i': " << i << " - " << " `j': " << j << std::endl;
      // Outputs the same thing as the following methods in a worse way.
  ECHO(VAR(i) << " - " << VAR(j)); // Still outputs "`i': 42 - `j': 51"
  ECHO(LV2(i, j));                 // Also outputs "`i': 42 - `j': 51"
}

The first print has the drawback to be boring to maintain. If you go to the release mode you have to track all of these prints and remove them, if you change the name of a variable you have to change it twice (assuming you're not using a perfect refactoring tool), and it is longer to write. The last version is simple to write, you don't have to haunt it to prevent them from printing, and they introduces no overhead in release mode. We are far from the dummy printf method, and it is cool.

The moral of this series is that there is a lot of debugging methods, and you have to know them for being able to adapt your method to the situation. For example, dmesg is very specific, but the other method were very hard to use in these situations.

I invite you to share yours in the comments. I'm sure that there is a lot of other method out there, just waiting for being learned!

5 comments:

  1. You can have a variadic version of it at http://miscdevs.blogspot.fr/2012/08/print-debugging.html

    ReplyDelete
    Replies
    1. I can understand that one would want to have a variadic version of the print method described here, but I think this a little overkilled. This is "just" a tip for writing print simpler than before. I don't think someone will ever print ten or more variables on the same line (I hope at least)... But that is just my personal point of view.

      Delete
  2. When I see that, I don't think it's that much "variadic" :)

    # define ARG_N( \
    _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
    _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
    _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
    _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
    _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
    _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
    _61, _62, _63, N, ...) N

    ReplyDelete
    Replies
    1. Too bad there is no '+1' button on comments :)

      Delete
    2. Hi, thank you for your code :) I'd like to ask you: is it possible to give to the function VAR() an argument passed by user input? Like this: type_of a; cin>>a; VAR(a) ?
      Thank you :)

      Delete