
Show HN: A dbg(...) macro for C++ - sharkdp
https://github.com/sharkdp/dbg-macro
======
the_duke
This seems very much inspired by the `dbg` macro recently introduced in Rust.
[1]

Which in turn was inspired by Haskell s `Debug.Trace` functions [2].

It's definitely a convenient tool if you can't use a debugger or want to
follow more complex interactions.

[1][https://doc.rust-lang.org/std/macro.dbg.html](https://doc.rust-
lang.org/std/macro.dbg.html)

[2]
[https://hackage.haskell.org/package/base-4.12.0.0/docs/Debug...](https://hackage.haskell.org/package/base-4.12.0.0/docs/Debug-
Trace.html)

~~~
solinent
Debuggers are practically useless for me, they aren't complex enough to meet
my minimum use-case. If I use a debugger--approximately two hours wasted if
I'm completely unaware where the problem is. If I instead try to spend ten
minutes logically explaining the program, usually I fix the problem and also
gain a deeper understanding of the mechanics of the program. The program is
usually multi-threaded, real-time.

A debugger gives you the state of the entire program, and one point in time. A
log allows you to focus, which gives you the state you actually care about, at
all the points in time where you care about it.

The time-evolution of the state of the program is practically the only thing
I'm interested in.

I find that code that has been written mainly with debuggers is often full of
tons of errors, often simple negations which have been worked around an even
number of times. Then you're stuck with the debugger, and there's no way to
reason about the program. The result is low quality.

~~~
apaprocki
Discussing your feelings towards debuggers would produce a better discussion
if you explained what complexity you find lacking? Nowadays they are
incredibly complex pieces of software (e.g., [https://rr-
project.org](https://rr-project.org)) _if_ you take the time to learn how to
apply what they offer to the kind of software you write.

Simple things that people like reasoning about and visualizing with printf()
statements can be done in a debugger more easily after using your brain and
logic to think about what the issue might be. Think a value got assigned a
value it should't have? You add log statements, rebuild, relink, reproduce;
or, you could just set a hardware watchpoint to print the backtrace whenever
it changes. In my experience people don't reach for the debugger way of doing
it simply because it might not be needed all the time and the syntax is
forgotten. Diving into `man` or docs _is_ harder than printf(). Use those
skills more regularly and/or make a cheatsheet, macros, etc. and it is less of
a problem.

Debuggers _must_ be used in certain situations. Personally, the canonical
example I use is tracking down compiler bugs. I and colleagues have been hit
with a number and it would be utterly hopeless to attempt to reason about what
is going on without a debugger. (One particular past case that stands out was
the compiler, under very certain circumstances, not restoring a register that
it was obligated to restore when emitting a catch block. Isolating the root
cause and generating a reduced example was _loads_ of fun /s, would not
recommend.) Then there's kernels, runtime engines with JIT, diagnosing
unreproducible problems in situ where the executable can't be replaced or
restarted, and the list goes on...

~~~
solinent
It's more of just a joke--sometimes a simpler solution can do much more than a
complex one. Complex solutions aren't necessitated, in fact, I actively avoid
them in my product.

It's also not for lack of use, I've developed software for more than fifteen
years, and I've always had access to some sort of debugger, and I've always
used them, just very rarely.

>You add log statements, rebuild, relink, reproduce; or, you could just set a
hardware watchpoint to print the backtrace whenever it changes. In my
experience people don't reach for the debugger way of doing it simply because
it might not be needed all the time and the syntax is forgotten. Diving into
`man` or docs is harder than printf(). Use those skills more regularly and/or
make a cheatsheet, macros, etc. and it is less of a problem.

Hardware watch points work horribly for objects, I have to create and maintain
views for all the practical objects I use, unless they're very popular
libraries like OpenCV. Ultimately, these views could simply be printf or cout
statements, maintained with a common function or method defined on all
objects.

These log statements typically happen right near assert statements, so they
can be maintained along with the code. I'll usually use something more
sophisticated than simple printf, and I'll have many different types of logs
for common debugging cases. They can be enabled or disabled at runtime, so no
recompilation needed. They give me access to all the state I could possibly
require, and they get committed to the code base.

I've had coworkers spend entire days in debuggers (often in groups), where a
simple step back and a simple log easily solves the problem.

I would use hardware watchpoints if they were standardized across debuggers,
but they don't have all the features I need even in the best debuggers, and
then I'm still useless on other platforms. Also, I usually need the program to
run at near real-time otherwise the bug may not reproduce itself.

edit: I should add I do use some of the more advanced debugger features in
those rare cases. So debuggers are still necessary, just not in the day-to-
day.

I should also say I've also worked in environments where the software has to
be pretty much bug-free, where small bugs may not be immediately noticeable.
So there's a lot of planning, design, even non-formal proofs of correctness
happening. If it's allowable for your software to have some bugs, maybe you
can take a shortcut with a debugger.

Finally, I'm not against all tooling, just debuggers. I use visual profilers
all the time.

------
asah
Would be nice to offer runtime enable/disable. Out of curiosity, I wrote a
little benchmark and submitted as a PR: [https://github.com/sharkdp/dbg-
macro/pull/57](https://github.com/sharkdp/dbg-macro/pull/57)

The key idea is that modern CPUs dynamically branch-predict-away if-statements
that are rarely/never invoked, such as a dynamically disabled dbg() call.

With the performance difference magnified 10x by loop unrolling, this is what
I'm seeing on my Mac laptop:

830000000 iterations in 3 secs = 2.76667e+08 iters/sec (compiled-out).
840000000 iterations in 3 secs = 2.8e+08 iters/sec (dynamically disabled).

And this is the worst case, where the whole program does nothing but call
dbg() - real world programs contain lots of other real work, drowning the
minute difference in performance, i.e. in a real program I doubt you'd see
even 0.1% total performance difference, littering it with dynamic dbg()
statements.

p.s. my C/C++ is pretty rusty - feedback welcome, but pls be kind.

~~~
xiphias2
This is not a real disabling of dbg.

dbg.h should support a function that disables writing out to the screen, but
still returning the expression during run-time.

You could try it out with something like this:

    
    
      disable_dbg_output();
      int a = 0;
      for(int i=0; i<1000000000; i++) {
        a += dbg(i);
      }

~~~
asah
yes, correct - my code is just the performance test, which IMHO is the first
step and surprisingly tricky due to effects from the loop around it.

The code for dynamic enable/disable is trivial but the design is a bit
subjective. For example, another API might be set_dbg_output(bool) and then
maybe get_dbg_output() to inspect the current state.

~~~
pnako
An alternative route might be to find a way to integrate this code with a
logging library like spdlog.

This way you'd get the cool short notation of dbg(...) and the ability to have
more control about the sinks (including indeed dynamic disabling/enabling).

------
s_gourichon
I wrote and use regularly similar macros in C++ context.

They are less advanced on some areas, but more advanced on some.

Particularly nice features:

* a structured log indented by stack trace depth (fully portable, put a simple macro at function start, will log start and any exit)

* log any expression: `somemacro(foo.bar())` will log `foo.bar() = 42`

* log scopes like functions

* with possibility to jump to source code line on one click or keypress

* browse the log with structured jumps (like step into function vs advance one line, back and forth)

...just by generating a text format that is parsed by common pre-existing
tools (namely emacs compilation-mode and a few other short settings).

I've been planning to share that for a moment. It will eventually appear on
[https://github.com/fidergo-stephane-
gourichon?tab=repositori...](https://github.com/fidergo-stephane-
gourichon?tab=repositories)

I wrote a similar set of macros for C, see my comment
[https://news.ycombinator.com/item?id=21071497](https://news.ycombinator.com/item?id=21071497)
on eerimoq's "Show HN"
[https://news.ycombinator.com/item?id=21040649](https://news.ycombinator.com/item?id=21040649)

------
fabrice_d
Mozilla has something similar in Gecko's codebase, inspired by the Rust macro:
[https://searchfox.org/mozilla-
central/source/mfbt/DbgMacro.h](https://searchfox.org/mozilla-
central/source/mfbt/DbgMacro.h)

~~~
jedimastert
Chromium's got "LOG" as well in it's debugging mode.

------
hellofunk
I wrote something similar a couple years ago, except my macro automatically
compiled to a no op if compiled in release mode, which is very convenient for
switching back-and-forth to the testing.

~~~
sharkdp
Thank you for the feedback. We deliberately chose not to do this (see
discussion in [https://github.com/sharkdp/dbg-
macro/issues/26](https://github.com/sharkdp/dbg-macro/issues/26)), mainly for
the reasons given in the Rust documentation:

> The dbg! macro works exactly the same in release builds. This is useful when
> debugging issues that only occur in release builds or when debugging in
> release mode is significantly faster.

Note, however, that the C++ _dbg(..)_ macro can be easily disabled to a no-op
(identity-op, to be precise) with the _DBG_MACRO_DISABLE_ flag.

------
bt848
This is neat. The obvious comparison is glog's DLOG feature.

~~~
empyrical
It also reminds me of Qt's qDebug macros

------
rightbyte
Oh ... this is really neat. I like how you can wrap an subexpression or
prettyprint vectors.

When the debugger is too cumbersome to use good old prints is always a nice
fallback.

------
DFHippie
The name reminds me of
[https://metacpan.org/pod/DBG](https://metacpan.org/pod/DBG), which, full
disclosure, I wrote. Not that it's nearly as cool or likely to be used by
anyone, but ... it has a superficial resemblance.

------
rat87
I did one of these for C(or at least gcc/llvm c) but it's a lot harder to
abstract over types in c so it's a bit more limited

[https://github.com/rtaycher/debug_print](https://github.com/rtaycher/debug_print)

------
pnako
It looks pretty cool. I agree that installing the header in /usr/include is a
good idea.

------
WhiteSage
Nice idea! I would appreciate a warning from the author specifying whether dbg
evaluates its argument twice, as if so one must be careful with side effects
when using it.

~~~
sharkdp
Thank you for the feedback. No, it does not evaluate its argument twice - see
this test: [https://github.com/sharkdp/dbg-
macro/blob/f30cdda9fc5332e062...](https://github.com/sharkdp/dbg-
macro/blob/f30cdda9fc5332e06201d886c9e6ec4b8f0f1216/tests/tests.cpp#L166-L168)

~~~
WhiteSage
Thanks for the reply. Amazing work :)

------
maurodelazeri
I was thinking about writing a lib like this... really useful

------
halfer53
It would be great if we can see similar things in Java or golang

------
israrkhan
would be nice if we had something similar for C...

~~~
eerimoq
See [https://github.com/eerimoq/dbg-macro](https://github.com/eerimoq/dbg-
macro).

I got inspired by this HN post and implemented it today. There's certainly
room for improvment.

------
97b683f8
in lisp this would be

    
    
        (defmacro dbg [expr]
          (println '~expr ~expr))

~~~
Ives
Not really, the C++ macro provides information about which source file the
statement was generated from, as well as color coding.

~~~
kazinator

      1> (defmacro dbg (expr)
           (with-gensyms (val)
             ^(let ((,val ,expr))
                (format t "~a: ~s -> ~s\n" (source-loc-str ',expr) ',expr ,val)
                ,val)))
      dbg
      2> (dbg (cons 1 2))
      expr-2:1: (cons 1 2) -> (1 . 2)
      (1 . 2)
      3> (dbg (+ 2 2))
      expr-3:1: (+ 2 2) -> 4
      4
      4> (progn
           (dbg (list 1 2))
           (dbg (cons 1 2))
           (dbg (+ 1 2)))
      expr-4:2: (list 1 2) -> (1 2)
      expr-4:3: (cons 1 2) -> (1 . 2)
      expr-4:4: (+ 1 2) -> 3
      3
    

Colorization is just inserting some trivial ANSI codes. Arguably, this just
slows down the program even more and increases the size of the log files. It
can be done as a post-processing filter.

------
einpoklum
This feels like too much of a "ricer" feature, at least the way it's
implemented.

Instead, you want:

* A pretty-printer library (possibly single-header) * A function for obtaining the typename; see:
    
    
        https://stackoverflow.com/q/35941045/1593077
        https://stackoverflow.com/a/56766138/1593077
    
      for a constexpr approach.
    

and it won't hurt to have:

* A logging library with log levels (so that you don't necessarily need to recompile to enable these outputs) * A stack trace printing library

When you have that, such a macro becomes nearly trivial. Oh, yeah, and - drop
the silly ANSI coloring.

~~~
stjohnswarts
He would be a lot more receptive if you didn't use terms like "ricer" and
seemingly demanding changes rather than recommending them.

~~~
quickthrower2
What’s a ricer?

