我正在编写一个针对arduino的manchester解码算法,我经常在尝试让事情工作时打印调试内容,但是打印到串口和字符串常量会增加很多开销。我不能把它放在最后的二进制文件中。
我通常只是通过代码删除任何调试相关的行。 我正在寻找一种轻松打开和关闭它的方法。
我知道的唯一方法就是这个
#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 ++功能允许我这样做而不是预处理器?
答案 0 :(得分:8)
冒着愚蠢的风险:是的,有一个C ++功能,它看起来像这样:
if (DEBUG)
{
// Your debugging stuff here…
}
如果DEBUG
是编译时常量(我认为使用宏是合理的,但在这种情况下不需要),编译器几乎肯定不会为调试内容生成任何代码(甚至不是分支) <{1}}在编译时是错误的。
在我的代码中,我喜欢有几个调试级别。然后我可以这样写:
debug
同样,如果条件在编译时为假,编译器将优化掉整个构造。
通过允许双倍调试级别,您甚至可以获得更多的幻想。在编译时启用的最大级别和在运行时使用的实际级别。
if (DEBUG_LEVEL >= DEBUG_LEVEL_FINE)
{
// Your debugging stuff here…
}
您可以if (MAX_DEBUG >= DEBUG_LEVEL_FINE && Config.getDebugLevel() >= DEBUG_LEVEL_FINE)
{
// Your debugging stuff here…
}
达到您希望在运行时选择的最高级别。在全性能构建中,您可以#define MAX_DEBUG
使条件始终为false并且根本不生成任何代码。 (当然,在这种情况下,您无法在运行时选择调试。)
但是,如果挤出最后一条指令并不是最重要的问题而且所有调试代码都是一些日志记录,那么通常的模式就像这样:
#define MAX_DEBUG 0
然后,各种函数将当前日志记录级别与函数名称指示的级别进行比较,如果较小,则立即返回。除紧环外,这可能是最方便的解决方案。
最后,我们可以通过为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_;
};
类提供inline
包装来组合这两个世界。
Logger
只要评估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
函数中的条件为假,编译器将消除调用的可能性很大。这就是为什么我添加了带有原始C字符串的重载,以优化使用字符串文字调用函数的常见情况。看看装配是否确定。
答案 1 :(得分:3)
我个人不会在我的代码中散布很多#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
,那么宏预处理器(而不是编译器)不会扩展该代码。
我的方法略有不足之处在于,虽然它使你的鳕鱼更干净,但它会强加额外的函数调用,即使没有定义DEBUG
。智能链接器可能会意识到被调用的函数是空的并且将删除函数调用,但我不会在它上面存在。
参考文献:
答案 2 :(得分:3)
如果标志为设置,我还建议使用内联函数变为空。为何设置?因为您通常希望始终进行调试,除非您编译发布版本。
由于已使用NDEBUG
,因此您也可以使用它来避免使用多个不同的标志。调试级别的定义也非常有用。
还有一件事要说:小心使用通过使用宏来改变的函数!你可以通过翻译代码的某些部分而不使用其他调试来轻松违反One Definition Rule。
答案 3 :(得分:0)
您可以遵循assert(3)的约定并使用
包装调试代码 #ifndef NDEBUG
DebugPrint("something");
#endif
有关实际示例,请参阅here(在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)
。因人而异。 (您将通过dodebug
命令或通过某个程序参数设置gdb
标志,可能使用getopt_long(3)进行解析,至少在Linux上进行解析。
PS。提醒do{
... }while(0)
是一个old trick来创建一个像宏一样的健壮语句(适用于普通语句所在的每个位置,例如,作为当时或者其他部分的一部分) if
等......)。
答案 4 :(得分:0)
您还可以使用C ++ 17中constexpr if
功能的模板。你根本不必担心预处理器,但是在使用模板时你的声明和定义必须在同一个地方。