使用宏增加c ++代码详细程度

时间:2013-02-11 13:15:58

标签: c++ debugging macros

我希望有可能为程序的调试目的增加详细程度。当然,我可以在运行时使用开关/标志来做到这一点。但由于我应该在代码中添加所有“if”语句,因此效率非常低。

所以,我想在编译期间添加一个标志,以便在我的代码中包含可选的,通常很慢的调试操作,而不会在不需要时影响程序的性能/大小。这是一个例子:

/* code */
#ifdef _DEBUG_
/* do debug operations here 
#endif

所以,使用-D_DEBUG_进行编译应该可以解决问题。没有它,那部分将不会包含在我的程序中。

另一个选择(至少对于i / o操作)是至少定义一个i / o函数,比如

#ifdef _DEBUG_
#define LOG(x) std::clog << x << std::endl;
#else
#define LOG(x) 
#endif

然而,我强烈怀疑这可能不是最干净的方法。那么,你会做什么呢?

10 个答案:

答案 0 :(得分:18)

我更喜欢将#ifdef与真实函数一起使用,以便在未定义_DEBUG_时函数具有空体:

void log(std::string x)
{
#ifdef _DEBUG_
  std::cout << x << std::endl;
#endif
}

这种偏好有三个重要原因:

  1. 如果未定义_DEBUG_,则函数定义为空,任何现代编译器都将完全优化对该函数的任何调用(当然,该定义应该在该翻译单元中可见)。
  2. #ifdef警卫只需要应用于较小的本地化代码区域,而不是每次拨打log时都会应用。
  3. 您不需要使用大量宏,从而避免污染您的代码。

答案 1 :(得分:2)

您可以使用宏来更改函数的实现(就像在sftrabbit的解决方案中一样)。这样,代码中就不会留下任何空位,编译器会优化“空”调用。

您还可以使用两个不同的文件进行调试和发布实现,并让您的IDE / build脚本选择合适的文件;这根本不涉及#defines。只需记住DRY规则,并在调试方案中重新使用干净的代码。

我会说他实际上非常依赖你所面临的实际问题。一些问题将使第二个解决方案受益更多,而简单的代码可能更好用简单的定义。

答案 2 :(得分:2)

您描述的两个片段都是使用条件编译通过编译时开关启用或禁用调试的正确方法。但是,你在运行时检查调试标志的断言“可能是非常低效的,因为我应该添加到我的代码中的所有'if'语句”大多是不正确的:在大多数实际情况下,运行时检查不会影响你的速度。以可检测的方式编程,因此如果保持运行时标志为您提供潜在的优势(例如,打开调试以在不重新编译的情况下诊断生产中的问题),您应该转而使用运行时标志。

答案 3 :(得分:1)

对于其他检查,我将依赖于assert(请参阅assert.h),它完全符合您的需要:检查何时在debug中编译,在编译发布时不检查。

对于详细程度,您建议的更多C ++版本将使用带有布尔值作为模板参数的简单Logger类。但是如果保留在Logger类中,宏也可以。

答案 4 :(得分:1)

对于商业软件,拥有在客户站点上运行时可用的一些调试输出通常是有价值的。我并不是说所有内容都必须编译成最终的二进制文件,但是客户对代码执行的操作并不是不寻常的[或者导致代码以您不期望的方式运行]。能够告诉客户“嗯,如果你运行myprog -v 2 -l logfile.txt并做你平常的事情,那么给我发电子邮件logfile.txt”是一件非常非常有用的事情。

只要“if-statement来决定我们是否登录”不在秘鲁最深,最黑暗的丛林中,呃,我的意思是在你的紧密,性能关键循环的最深的嵌套水平,那么它很少留下它的问题。

所以,我个人倾向于选择“始终存在,而不是始终启用”的方法。这并不是说我没有发现自己有时会在我的紧密循环中间添加一些额外的日志记录 - 只是在以后修复bug时将其删除。

答案 5 :(得分:0)

在进行条件编译时,可以避免使用类似函数的宏。只需定义一个常规或模板函数来进行日志记录并在:

中调用它
#ifdef _DEBUG_
/* ... */
#endif

部分代码。

答案 6 :(得分:0)

我在项目中使用的一段示例代码。这样,您可以使用变量参数列表,如果未设置 DEBUG 标志,则清除相关代码:

#ifdef DEBUG
#define PR_DEBUG(fmt, ...) \
    PR_DEBUG(fmt, ...) printf("[DBG] %s: " fmt, __func__, ## __VA_ARGS__)
#else
#define PR_DEBUG(fmt, ...)
#endif

用法:

#define DEBUG
<..>
ret = do_smth();
PR_DEBUG("some kind of code returned %d", ret);

输出:

[DBG] some_func: some kind of code returned 0

当然,printf()可能会被您使用的任何输出函数替换。此外,它可以很容易地修改,以便自动附加附加信息,例如时间戳。

答案 7 :(得分:0)

至少在* Nix Universe中,此类事物的默认定义是NDEBUG(读取 no-debug )。如果已定义,则代码应跳过调试代码。即你会做这样的事情:

#ifdef NDEBUG
inline void log(...) {}
#else
inline void log(...) { .... }
#endif

答案 8 :(得分:0)

对我来说,这取决于从申请到申请。

我有应用程序我想要始终记录(例如,我们有一个应用程序,如果出现错误,客户端将获取应用程序的所有日志并将它们发送给我们进行诊断)。在这种情况下,日志记录API可能应该基于函数(即非宏)并始终定义。

如果并不总是需要记录,或者出于性能/其他原因需要完全禁用它,则可以定义记录宏。

在这种情况下,我更喜欢这样的单行宏:

#ifdef NDEBUG
#define LOGSTREAM /##/
#else
#define LOGSTREAM std::clog
// or
// #define LOGSTREAM std::ofstream("output.log", std::ios::out|std::ios::app)
#endif

客户代码:

LOG << "Initializing chipmunk feeding module ...\n";
//...
LOG << "Shutting down chipmunk feeding module ...\n";

答案 9 :(得分:0)

就像任何其他功能一样。

我的假设:

  • 没有全局变量
  • 设计用于接口的系统

对于你想要的详细输出,创建两个实现,一个安静,一个详细。 在应用程序初始化时,选择您想要的实现。

例如,它可以是记录器,窗口小部件或内存管理器。

显然,您不想复制代码,因此请提取所需的最小变体。如果你知道策略模式是什么,或者装饰模式,这些都是正确的方向。遵循开放的封闭原则。