如何分离调试模式和发布模式的代码

8

在调试模式或测试期间,我需要打印大量各种信息,因此我使用以下方法:

#ifdef TESTING
// code with lots of debugging info
#else
// clean code only
#endif // TESTING`

这是一种好的方法吗,还是有其他简单而优雅的方法?

但是这样做,我在两个地方重复了相同的代码,如果以后要更改代码中的任何内容,我必须在两个地方进行更改,这很耗时且容易出错。

谢谢。

我正在使用MS Visual Studio。

7个回答

17
你可以使用宏来打印调试信息,然后在发布版本中,将该宏定义为空。
例如,
#ifdef _DEBUG
#define DEBUG_PRINT(x) printf(x);
#else
#define DEBUG_PRINT(x)
#endif

使用这种方法,您还可以添加更多的信息,例如

__LINE__ 
__FILE__

自动获取调试信息。


也许吧,但问题标记了C和C++两种语言。 - Tuomas Pelkonen
我不同意Alexey的观点,我用C++写了多年,也使用宏定义而没有遇到任何问题。也许这是因为我的C++风格比较好,对于那些不知道自己在做什么的糟糕程序员来说,可能会出现问题。但只有通过宏定义,我才能完成一些非常困难的任务。使用多种方式的自由并不是那么糟糕 :) - Aristos
5
宏通常能够使编写类型不安全、复杂和/或不清晰的代码更容易。有些事情你无法用其他方式完成,而宏比复制黏贴相同代码更好。 - Mark B
2
在上述情况下,除了使用宏之外,是否有其他替代方案是最简单的解决方案? - john
我从未为任何一家主要的软件公司工作过,他们不在C++代码中使用宏。虽然它们不是首选,但有时它们是唯一现实的选择,特别是在性能关键的代码中,例如视频游戏。 - Kaitain
显示剩余2条评论

7
写一次
#ifdef _DEBUG 
const bool is_debig = true;
#else 
const bool is_debig = false;
#endif 

并且然后。
template<bool debug>
struct TemplateDebugHelper {
   void PrintDebugInfo(const char* );
   void CalcTime(...);
   void OutputInfoToFile(...);
   /// .....
};

// Empty inline specialization
template<>
struct TemplateDebugHelper<false> {
   void PrintDebugInfo(const char* ) {} // Empty body
   void CalcTime(...) {} // Empty body
   void OutputInfoToFile(...) {} // Empty body
   /// .....
};

typedef TemplateDebugHelper<is_debug> DebugHelper;

DebugHelper global_debug_helper;

int main() 
{
   global_debug_helper.PrintDebugInfo("Info"); // Works only for is_debug=true
}

我可以问一下,这是否在发布模式下留下了任何编译文件的代码? - Aristos
最坏的情况下,它将是一个空函数cal。然而,根据我的经验,由于代码必须对编译器可用,即使那个也会被优化掉。 - Dennis Zickefoose
1
除此之外,我喜欢这个答案。但是我的经验告诉我,如果程序员必须写“global_debug_helper.PrintDebugInfo”,他就不会费心去写它。我通常使用短的全大写字母,这样写起来容易,也容易发现:BUG.OUT("Info"); - swestrup
@Dennis:我担心的不是函数调用,而是参数的评估。我不确定模板解决方案是否允许编译器跳过参数的评估,当你在调试模式下进行大量转储时,你希望它被删除。 - Matthieu M.
这完全取决于你在参数中做了什么。如果编译器能够确定没有副作用,那么它将会快乐地跳过它们。 - Dennis Zickefoose

2

在包含头文件时使用类似于这样的定义

#ifdef  TESTING
#define DEBUG_INFOS(_X_) CallYourDebugFunction(_X_ )
#else
#define DEBUG_INFOS(_X_) ((void)0)
#endif

然后在您的代码中仅使用此内容。

...
DEBUG_INFOS("infos what ever");
RestOfWork();
...

你还可以使用和查找ASSERTTRACE宏,并使用sysinternals的DebugView实时读取跟踪输出,或者跟踪ASSERT中出现的问题。ASSERT和TRACE执行类似的工作,你可以从它们那里获取想法。

备注:我使用了TESTING声明,因为我在问题中看到了它。


2
请使用Log4Cxx来替代自己编写日志记录。Log4Cxx包具有高度可配置性,支持基于重要/严重程度的不同级别的日志记录,并支持多种形式的输出。
此外,除非代码非常关键且必须进行超级优化,否则建议在代码中保留日志记录语句(假设您使用了Log4Cxx),并仅降低日志记录级别。这样,可以动态启用日志记录,如果用户遇到难以复制的错误,那么这将非常有帮助... 只需指导他们如何配置更高的日志记录级别即可。如果从可执行文件中完全省略日志记录,则无法获取该宝贵的调试输出。

1
在我的团队项目中,我们发现编译TRACE消息对性能没有明显影响,除非我们实际启用跟踪,而log4xxx框架的美妙之处在于您可以针对某些日志源启用跟踪,而对其他日志源则不启用,具体取决于您要定位什么。在我们的代码中有一些地方,跟踪日志调用的参数计算成本很高;对于这些情况,我们只需调用Logger::isEnabledFor(LogLevelTrace)来检测给定记录器是否开启了跟踪;如果是,则构建昂贵的参数并记录跟踪消息,否则我们就不管它。 - anelson
@anelson,为什么你没有使用LOG4CXX_TRACE(LOGGER,MSG)?我相信这个宏已经为你做了那个检查。 - Michael Aaron Safyan

1
你可以使用类似 boost::log 的东西,将严重程度级别设置为所需级别。
void init()
{
    logging::core::get()->set_filter
    (
        flt::attr< logging::trivial::severity_level >("Severity") >= logging::trivial::info
    );
}

int main(int, char*[])
{
    init();

    BOOST_LOG_TRIVIAL(trace) << "A trace severity message";
    BOOST_LOG_TRIVIAL(debug) << "A debug severity message";
    BOOST_LOG_TRIVIAL(info) << "An informational severity message";
    BOOST_LOG_TRIVIAL(warning) << "A warning severity message";
    BOOST_LOG_TRIVIAL(error) << "An error severity message";
    BOOST_LOG_TRIVIAL(fatal) << "A fatal severity message";
}

我认为pantheios也有类似的东西。


0

有一种简单的方法,适用于大多数编译器:

#ifdef TESTING
    #define DPRINTF( args )     printf args
#else
    #define DPRINTF( args )     ((void)0)
#endif

接下来,在源代码中您应该这样使用它:

DPRINTF(("Debug: value a = %d, value b = %d\n", a, b));

缺点是你必须使用双括号,但在旧的 C 和 C++ 标准中,可变参数宏不受支持(只能作为编译器扩展)。


0

我用C语言编写嵌入式系统程序。在我的程序中,我使用以下宏:

#define _L  log_stamp(__FILE__, __LINE__)
#define _LS(a) log_string(a)
#define _LI(a) log_long(a)
#define _LA(a,l) log_array(a,l)
#define _LH(a) log_hex(a)
#define _LC(a) log_char(a)
#define ASSERT(con) log_assert(__FILE__, __LINE__, con)

当我制作发布版本时,我只需关闭 #define DEBUG 指令,所有宏都变为空。请注意,在发布版本中不会消耗任何 CPU 循环和内存。宏是保存日志信息的唯一方式:记录日志的位置(文件和行号)。

如果需要此信息,则使用:_L;_LS("this is a log message number ");_LI(5);

否则就不需要 _L 指令。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接