编译时从发布二进制文件中删除调试打印

Compile time removing debug prints from release binaries

本文关键字:删除 调试 打印 二进制文件 编译      更新时间:2023-10-16

这是我的第一篇帖子,所以我想欢迎大家。我遇到的问题是编译时的代码优化,更具体地说,删除调试打印。

让我们想象一下,我们有原生的syslog logger,我们用以下代码包装它(不使用宏,这是非常重要的!):

enum severity { info_log, debug_log, warning_log, error_log };
template <severity S>
struct flusher {
  logger* log_;
  flusher(logger* log) : log_(log) {}
  flusher(flusher& rhs) : log_(rhs.log_) {}
  ~flusher() { syslog(S, log_->stream.str()); log_->stream.str(""); }
  operator std::ostream& () { return log_->stream; }
};
#ifdef NDEBUG
template <> struct flusher<debug_log> {
  flusher(logger*) {}
  flusher(flusher&) {}
  ~flusher() {}
  template <typename T> flusher& operator<<(T const&) { return *this; }
};
#endif
struct logger {
  std::ostringstream stream;
  template <severity T>
  flusher<T> operator<<(flusher<T> (*m)(logger&)) { return m(*this); }
};
inline flusher<info_log> info(logger& log) { return flusher<info_log>(&log); }
inline flusher<debug_log> debug(logger& log) { return flusher<debug_log>(&log); }
inline flusher<warning_log> warning(logger& log) { return flusher<warning_log>(&log); }
inline flusher<error_log> error(logger& log) { return flusher<error_log>(&log); }

我认为flush的空实现会鼓励编译器删除这些无用的代码,但对于O2O3,它并没有被删除。

是否有可能引发上述行为?

提前感谢

我已经成功地完成了您的尝试,尽管至少有两个区别。。。1) 我没有使用模板——这可能会造成编译器无法优化的复杂性,2)我的日志使用包括一个宏(见下文)。

此外,您可能已经这样做了,请确保所有"空"定义都在记录器的头文件中(因此优化是在编译时完成的,而不是推迟到链接时)。

// use it like this
my_log << "info: " << 5 << endl;

发布定义如下:

#define my_log if(true);else logger

调试定义如下所示:

#define my_log if(false);else logger

请注意,编译器在发行版中优化了所有if(true)的记录器,并在调试中使用记录器。还要注意,在这两种情况下,完整的if/else语法都避免了使用非范围的有趣情况,例如

if (something)
    my_log << "this" << endl;
else
    somethingelse();

会导致somethingelse是my_log的else,而没有它。

您当前的代码并没有阻止对f()的调用及其可能产生的任何副作用,只是阻止了实际打印。这就是为什么宏是解决这个问题的传统方法——它们提供了一个未评估的上下文,您可以在实际打印之前检查是否应该打印值。

为了在没有宏的情况下实现这一点,需要一些额外的间接性,例如std::函数、函数指针等。例如,您可以提供一个包含std:函数的包装器类,并专门指定流运算符在默认情况下调用std:功能,而不是在NDEBUG情况下调用

非常粗略的例子:

//Wrapper object for holding std::functions without evaluating
template <typename Func>
struct debug_function_t {
    debug_function_t(Func & f) : f(f) {}
    decltype(f()) operator()() { return f(); }
    std::function<Func> f;
};
//Helper function for type deduction
template <typename Func>
debug_function_t<Func> debug_function(Func & f) { 
    return debug_function_t<Func>(f);
}
struct debug_logger {
    template <typename T>
    debug_logger & operator<<(T & rhs) {}
    template <typename Func> //Doesn't call f(), so it's never evaluated 
    debug_logger & operator<<(debug_function_t<Func> f) { } 
};

然后在你的客户端代码

int f(){ std::cout << "f()n"; }
debug_logger log;
log << debug_function(f);

因此,遵循注释的代码:

inline int f() 
{ 
  std::cout << 1; 
  return 1; 
}

需要制作成:

inline int f() 
{ 
#ifndef NDEBUG
   std::cout << 1; 
#endif
   return 1; 
}

或者类似的东西:

#ifndef NDEBUG
static const int debug_enable = 1;
#else
static const int debug_enable = 0;
#endif    

inline int f() 
{ 
   if (debug_enable)
   {
       std::cout << 1; 
   }
   return 1; 
}

您需要以某种方式告诉编译器不需要此代码。

我在几款游戏中使用的技术要求调试打印是一个函数,而不是一个通用表达式。例如:

debug_print("this is an error string: %s", function_that_generates_error_string());

在释放模式下,debug_print的定义为:

#define debug_print sizeof

这将从可执行文件中删除debug_print和传递给它的任何表达式。它仍然需要传递有效的表达式,但它们不会在运行时求值。