4

I just finished programming a large project in C. I have a lot of debugging code (printfs and what not within the code). Now those debugging statements are a performance overhead and I want to remove them but they are very helpful in the future for trouble shooting purposes. What are the best practices to put the debug code in the code base. I have across many options.

  1. Use a terminal argument (let's say -d) to specify if the executable should run in debug mode or not. The advantage is that the code will not change later. The disadvantage is that the code will be full of conditionals to check if the program is in debug mode or not

  2. The other solution is to use some C macros for debugging. I don't quite understand how this works. It seems like you would define d_printf that would be printf if you are in debug mode and nothing if you are in not. But I guess this will requires recompiling the code every time you change from a debug mode to a non debug mode.

Can experienced C professionals advise about what the best practises are?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Keeto
  • 4,074
  • 9
  • 35
  • 58
  • How much overhead is there really for the conditionals to check if it's in debug mode, when it ends up not printing anything? Especially if you can encapsulate the check into a function so your main code isn't littered with if statements. – Random832 Jul 27 '15 at 23:50
  • 2
    If #1 is an option, why not leave your program as is and just redirect stdoud to `/dev/null`? – Red Alert Jul 27 '15 at 23:50
  • 1
    Both of the methods you listed are very reasonable, and your evaluations of them are accurate. It seems like you answered your own question. – Adrian Jul 27 '15 at 23:51
  • 1
    In general I prefer using something like log4j, log4cpp, etc. That way you don't need to embed a bunch of junk code to test for debug/not debug, you can control log levels, etc. – Brian Vandenberg Jul 27 '15 at 23:56
  • 1
    Note [C `#define` macro for debug printing](http://stackoverflow.com/questions/1644868/c-define-macro-for-debug-printing/1644898#1644898) for some implementation ideas. – Jonathan Leffler Jul 28 '15 at 00:21

2 Answers2

4

Personally I like the ability to turn on debug without needing a recompile to enable debug.

I normally have one debug method that checks a command line arg or environment variable and output messages <= a given priority.

e.g.

void debug(int level, char *msg)
{
    if (level <= currentDebugLevel) {
        printf("%s\n", msg); /* or to stderr, or to file
    }
}

You can make it a varargs func so you can use the same as printf

John3136
  • 28,809
  • 4
  • 51
  • 69
  • Another method, instead of a level, would be a debug message type flag. If the flag is set, print the message, otherwise, skip. This is useful if you wish to limit debug messages to certain types rather than a generic level. – Fiddling Bits Jul 27 '15 at 23:58
  • 1
    this is a very good method because, most formal standards, like RTCA DOD-178x require the code under test be the same code as actually shipped in the product. – user3629249 Jul 28 '15 at 00:00
  • Yep, flags work as well. I've used both methods, but tend to use the levels as my starting point. I normally only use about 3 levels - kind of similar to the Java logger's "Info" "Warning" and "Error". – John3136 Jul 28 '15 at 00:00
  • An alternative would be to have a one `FILE*` variable for each debugging level, and set them based on the desired debug level. Gives you nice syntax with `fprintf`, e.g. `fprintf(warning_stream, "%s\n", "warning")` – Red Alert Jul 28 '15 at 00:16
  • 1
    @RedAlert quite nice as long as you timestamp the messages - sometimes it's nice to have them all in one file so you can figure out which bad thing happened first. – John3136 Jul 28 '15 at 00:17
  • @John3136 in most cases the `FILE*` ptrs would just be aliases for `/dev/null/` or `stdout`, but could definitely be expanded to do more. – Red Alert Jul 28 '15 at 00:21
  • I'd implement it as a macro so you can add add a meaningful value for `__FILE__`, `__LINE__`, and `__func__` to every call, – Andrew Henle Jul 28 '15 at 03:37
2

Best practices? Don't use printf() or fprintf() to stdout or stderr redirected to a file, especially for long-lived processes. Those methods can be adequate for casual debugging, but they have serious shortcomings for long-lived processes that can produce large amounts of data.

If you use printf() or fprintf() to such fixed streams or files, you become tied to those file(s) and can't easily move the logging to another file, or you can't move the data stream at all, depending on a lot of specifics. So without restarting your process you can't move the log file as part of a log file aging process nor can you truncate or otherwise remove the file if it fills up your disk space.

And there's no need to badly reinvent the wheel. Just about every OS provides a logging capability designed to scale and to meet the requirement that a log file be separable from the process doing the logging. Unix/Linux systems provide syslog (or rsyslog under the hood), Windows system have the EventLog API. Once you learn how to use and configure those, they're a lot easier to configure, code for, and use than almost any add-on logging product.

Also, define debugging macros using the __func__, __FILE__, and __LINE__ predefined values in your debug output:

#define DEBUGLOG( level, ... ) \
do \
{ \
    if ( ( level ) < currentDebugLevel ) \
    { \
        debugFuncCall( ( level ), __func__, __FILE__, __LINE__, __VA_ARGS__ ); \
    } \
} \
while ( 0 )

That will provide very useful debugging data for very little effort. You can define simple debug macros such as ENTER_METHOD( args...) and EXIT_METHOD( retval ) macros that at the appropriate level of debugging will allow you to trace everything your program does.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56