编译时从发布二进制文件中删除调试打印
Compile time removing debug prints from release binaries
这是我的第一篇帖子,所以我想欢迎大家。我遇到的问题是编译时的代码优化,更具体地说,删除调试打印。
让我们想象一下,我们有原生的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的空实现会鼓励编译器删除这些无用的代码,但对于O2
和O3
,它并没有被删除。
是否有可能引发上述行为?
提前感谢
我已经成功地完成了您的尝试,尽管至少有两个区别。。。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
和传递给它的任何表达式。它仍然需要传递有效的表达式,但它们不会在运行时求值。
- 有没有一种预处理器的方法可以从调试符号中删除代码段
- 从静态库中去除/删除调试符号和存档名称
- Nodejs本机模块 - 如何删除调试符号
- GNU 调试器无法从显示中删除变量?
- 单例:为什么不需要删除并且看不到析构函数调试消息
- 链表调试(删除C++关键字神秘)?
- 删除SDL_VideoInfo指针时调试断言失败
- 正在删除导致调试断言的指针
- 为什么当我删除此字符 * 时,调试断言失败block_type_is_valid
- 删除对象时调试断言失败
- 删除目录的函数仅在调试完成 c++ 后将其删除
- 如何在C++中调试双重删除
- NDK如何在发布时删除日志调试语句
- 删除时出现C++内存错误-调试断言失败
- 删除Xcode 5.1项目中的所有调试表达式
- 是否有可能调试最初由可执行文件生成的已删除符号的核心文件?
- 删除Visual c++ 2003中的调试符号
- Linux 上 eclipse 中的清理项目不会删除调试/发布文件夹
- 编译时从发布二进制文件中删除调试打印
- 在发布代码c++中删除调试代码