Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Way to toggle debugging code on and off

Tags:

c++

debugging

I was programming a manchester decoding algorithm for arduino, and I often had to print debug stuff when trying to get things working, but printing to serial and string constants add a lot of overhead. I can't just leave it there in the final binary.

I usually just go through the code removing anything debug related lines. I'm looking for a way to easily turn it on and off.

The only way I know is this

 #if VERBOSE==1
     Serial.println();
     Serial.print(s);
     Serial.print(" ");
     Serial.print(t);
     Serial.print(" preamble");  
 #endif

...

 #if VERBOSE==1
     Serial.println(" SYNC!\n");
 #endif

and on top of the file I can just have

#define VERBOSE 0 // 1 to debug

I don't like how much clutter it adds to single liners. I was very tempted to do something very nasty like this. But yeah, evil.

Change every debug output to

verbose("debug message");

then use

#define verbose(x) Serial.print(x) //debug on

or

#define verbose(x) //debug off

There's a C++ feature that allows me to just do this instead of preprocessor?

like image 239
Vitim.us Avatar asked Dec 06 '22 22:12

Vitim.us


2 Answers

At the risk of sounding silly: Yes, there is a C++ feature for this, it looks like this:

if (DEBUG)
  {
     // Your debugging stuff here…
  }

If DEBUG is a compile-time constant (I think using a macro is reasonable but not required in this case), the compiler will almost certainly generate no code (not even a branch) for the debugging stuff if debug is false at compile-time.

In my code, I like having several debugging levels. Then I can write things like this:

if (DEBUG_LEVEL >= DEBUG_LEVEL_FINE)
  {
     // Your debugging stuff here…
  }

Again, the compiler will optimize away the entire construct if the condition is false at compile-time.

You can even get more fancy by allowing a two-fold debugging level. A maximum level enabled at compile-time and the actual level used at run-time.

if (MAX_DEBUG >= DEBUG_LEVEL_FINE && Config.getDebugLevel() >= DEBUG_LEVEL_FINE)
  {
     // Your debugging stuff here…
  }

You can #define MAX_DEBUG to the highest level you want to be able to select at run-time. In an all-performance build, you can #define MAX_DEBUG 0 which will make the condition always false and not generate any code at all. (Of course, you cannot select debugging at run-time in this case.)

However, if squeezing out the last instruction is not the most important issue and all your debugging code does is some logging, then the usual pattern lokks like this:

class Logger
{
public:

  enum class LoggingLevel { ERROR, WARNING, INFO, … };

  void logError(const std::string&) const;
  void logWarning(const std::string&) const;
  void logInfo(const std::string&) const;
  // …

private:

  LoggingLevel level_;
};

The various functions then compare the current logging level to the level indicated by the function name and if it is less, immediately return. Except in tight loops, this will probably be the most convenient solution.

And finally, we can combine both worlds by providing inline wrappers for the Logger class.

class Logger
{
public:

  enum class LoggingLevel { ERROR, WARNING, INFO, … };

  void
  logError(const char *const msg) const
  {
    if (COMPILE_TIME_LOGGING_LEVEL >= LoggingLevel::ERROR)
      this->log_(LoggingLevel::ERROR, msg);
  }

  void
  logError(const std::string& msg) const
  {
    if (COMPILE_TIME_LOGGING_LEVEL >= LoggingLevel::ERROR)
      this->log_(LoggingLevel::ERROR, msg.c_str());
  }

  // …

private:

  LoggingLevel level_;

  void
  log_(LoggingLevel, const char *) const;
};

As long as evaluating the function arguments for your Logger::logError etc calls does not have visible side-effects, chances are good that the compiler will eliminate the call if the conditional in the inline function is false. This is why I have added the overloads that take a raw C-string to optimize the frequent case where the function is called with a string literal. Look at the assembly to be sure.

like image 54
5gon12eder Avatar answered Dec 25 '22 19:12

5gon12eder


Personally I wouldn't have a a lot of #ifdef DEBUG scattered around my code:

#ifdef DEBUG
   printf("something");
#endif
  // some code ...
#ifdef DEBUG
   printf("something else");
#endif

rather, I would wrap it in a function:

void DebugPrint(const char const *debugText)  // ToDo: make it variadic [1]
{
#ifdef DEBUG
  printf(debugText);
#endif
}

DebugPrint("something");
// some code ...
DebugPrint("something else");

If you don't define DEBUG then the macro preprocessor (not the compiler) won't expand that code.

The slight downside of my approach is that, although it makes your cod cleaner, it imposes an extra function call, even if DEBUG is not defined. It is possible that a smart linker will realize that the called function is empty and will remove the function calls, but I wouldn't bank on it.

References:

  1. “Variadic function” in: Wikipedia, The Free Encyclopedia.
like image 37
Mawg says reinstate Monica Avatar answered Dec 25 '22 19:12

Mawg says reinstate Monica