打开和关闭调试代码的方法

Way to toggle debugging code on and off

本文关键字:代码 方法 调试      更新时间:2023-10-16

我正在为arduino编程一个曼彻斯特解码算法,当我试图让事情正常工作时,我经常不得不打印调试内容,但打印到串行和字符串常量会增加很多开销。我不能把它留在最后的二进制文件中。

我通常只是浏览代码,删除任何与调试相关的行。我正在寻找一种轻松打开和关闭它的方法。

我唯一知道的是这个

 #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

在文件的顶部,我可以有

#define VERBOSE 0 // 1 to debug

我不喜欢它给单行线增加了那么多混乱。我很想做这样非常恶劣的事情。但是,是的,邪恶。

将每个调试输出更改为

verbose("debug message");

然后使用

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

#define verbose(x) //debug off

有一个C++功能可以让我只做这件事,而不是预处理器?

冒着听起来很傻的风险:是的,有一个C++功能,它看起来像这样:

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

如果DEBUG是编译时常数(我认为使用宏是合理的,但在这种情况下不是必需的),那么如果debug在编译时为false,编译器几乎肯定不会为调试人员生成任何代码(甚至不会生成分支)。

在我的代码中,我喜欢有几个调试级别。然后我可以写这样的东西:

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

同样,如果条件在编译时为false,编译器将优化掉整个构造。

您甚至可以通过允许双重调试级别来获得更高级的功能。编译时启用的最大级别和运行时使用的实际级别。

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

您可以将#define MAX_DEBUG设置为希望在运行时选择的最高级别。在全性能构建中,您可以#define MAX_DEBUG 0,这将使条件始终为false,并且根本不生成任何代码。(当然,在这种情况下,您不能选择在运行时调试。)

然而,如果挤出最后一条指令不是最重要的问题,并且调试代码所做的只是一些日志记录,那么通常的模式是这样的:

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_;
};

然后,各种函数将当前日志记录级别与函数名称指示的级别进行比较,如果小于,则立即返回。除了在紧密的循环中,这可能是最方便的解决方案。

最后,我们可以通过为Logger类提供inline包装器来组合这两个世界。

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;
};

只要为Logger::logError等调用评估函数参数没有明显的副作用,那么如果inline函数中的条件为false,编译器很可能会消除该调用。这就是为什么我添加了使用原始C字符串的重载,以优化使用字符串文字调用函数的频繁情况。查看组件以确定。

就我个人而言,我的代码中不会有很多#ifdef DEBUG

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

相反,我会将其封装在一个函数中:

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

如果不定义DEBUG,那么宏预处理器(而不是编译器)将不会扩展该代码。

我的方法的一个小缺点是,尽管它使您的cod更干净,但它强制执行了一个额外的函数调用,即使没有定义DEBUG。智能链接器可能会意识到被调用的函数是空的,并删除函数调用,但我不会指望它

参考文献:

  1. "变分函数"在:维基百科,自由百科全书

我还建议使用内联函数,如果设置了标志,这些函数将变为空。为什么设置好了?因为除非编译发布版本,否则您通常希望始终进行调试。

因为已经使用了NDEBUG,所以您也可以使用它来避免使用多个不同的标志。调试级别的定义也非常有用。

还有一件事要说:小心使用通过使用宏更改的函数!通过在不禁用调试的情况下翻译代码的某些部分和其他部分,您很容易违反"一个定义规则"。

您可以遵循assert(3)的约定,并使用包装调试代码

 #ifndef NDEBUG
 DebugPrint("something");
 #endif

请参阅此处(在StackOverflow上,这将是一个更好的询问位置)以获得一个实用的示例。

在更像C++的风格中,你可以考虑

ifdef NDEBUG
#define debugout(Out) do{} while(0)
#else
extern bool dodebug;
#define debugout(Out) do {if (dodebug) {                 
  std::cout << __FILE__ << ":" << __LINE__ 
  << " " << Out << std::endl; 
 }} while(0)
#endif

然后在程序中使用debugout("here x=" << x)。YMMV。(您将通过gdb命令或某个程序参数设置dodebug标志,至少在Linux上可以使用getopt_long(3)进行解析)。

PS。提醒一下do{。。。}while(0)是制作健壮语句类宏的老技巧(适用于普通语句所在的每个位置,例如作为if的then或else部分等)

您还可以使用利用C++17中constexpr if特性的模板。您根本不必担心预处理器,但在使用模板时,您的声明和定义必须位于同一位置。

相关文章: