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!
You can have a variadic version of it at http://miscdevs.blogspot.fr/2012/08/print-debugging.html
ReplyDeleteI 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.
DeleteWhen I see that, I don't think it's that much "variadic" :)
ReplyDelete# 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
Too bad there is no '+1' button on comments :)
DeleteHi, 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) ?
DeleteThank you :)
Sixth is v. https://imgur.com/a/i9yHEgH https://imgur.com/a/ASPNMDc https://imgur.com/a/J6tKmAG https://imgur.com/a/Fk1WuLk https://imgur.com/a/qvlzkiT https://imgur.com/a/e6EV4Iv https://imgur.com/a/NSefwh6
ReplyDeleteThanks and I have a keen supply: Full House Reno home remodel
ReplyDelete