在 gcc 中禁用"if(0)"消除

Disable "if(0)" elimination in gcc

本文关键字:消除 if gcc      更新时间:2023-10-16

如何防止 GCC 消除 if(0) 块中的代码?

当我使用Visual Studio时,我的调试技术之一是将这样的代码放在我的程序中:

if (0)
    do_some_debug_printing_and_checking();

然后,当遇到断点时,我单击 do_some_debug_printing_and_checking() 行,选择"设置下一个语句"并强制执行。

当我使用 gcc/gdb 作为后端时,"set next 语句"不再起作用,因为 gcc 只是从 if(0) 语句中删除了代码。

我当然使用 -O0 标志来禁用优化。我也尝试了 -fno-dce -fno-tree-dce 标志来显式禁用死代码消除,但它没有效果:if(0) 的内容只是在二进制文件中不存在,我无法使用 set next 语句跳转到它。

有什么好方法可以告诉 gcc 禁用 if(0) 内容的消除?

编辑:

感谢您的"附加变量"解决方法,但是有两件事我不喜欢它:

  1. 它仍然是一行额外的代码
  2. 当我构建发布版本并希望这些调试内容消失时,它不会自动优化。当然,我可以使用 #ifdef-s,但这甚至是更多的额外行。

真的,绝对没有选择让 GCC 保留死代码?

最简单的方法是使检查依赖于(例如)具有外部链接的变量。

例如

extern bool debug;
if (debug)
    do_some_debug_printing_and_checking();

命名空间范围内的某个地方:

bool debug = false;

我不会依赖 gcc 编译器标志来做到这一点。 编译器标志可以跨 gcc 版本更改,并且可以跨编译器更改。 您可能会发现自己需要在六个月内在 Visual C++ 中调试相同的代码......

@CharlesBailey 对如何使用 extern 变量执行此操作提出了很好的建议。 这里有一种不需要向整个模块公开变量或保存在静态存储中的替代方法。

if 语句的作用域中声明一个临时变量volatile

if (volatile bool dbg = false)
{
  do_some_debug_printing_and_checking();
}

这使得临时变量的范围非常窄。 volatile限定符不允许编译器假设有关变量的任何内容,也不会优化分支。

要记住的一件事是,变量始终在堆栈上分配,并将保留在堆栈上,直到函数退出。 这种方法和extern方法都应该有效,但权衡略有不同(可能可以忽略不计)。

如果您愿意使用宏来帮助解决此问题,那么您可以在将代码发布到生产环境时轻松禁用临时变量:

#ifndef IS_DEBUGGING
#  define IS_DEBUGGING 0
#endif
#if IS_DEBUGGING
#  define TMP_DBG_FLAG volatile bool dbg_flag = false
#else
#  define TMP_DBG_FLAG false
#endif

然后将您的if语句声明为:

if ( TMP_DBG_FLAG )
{
  do_some_debug_printing_and_checking();
}

将IS_DEBUGGING定义为 1 时,将创建局部变量、声明为易失性并保留局部变量。 将IS_DEBUGGING定义为 0 时,宏将扩展到常量false,编译器将优化分支。 对于extern方法,也可以做一些非常类似的事情。

这几行额外的代码,但它们与你使用TMP_DBG_FLAG的次数无关。 该代码也比使用大量ifdef更具可读性。 可以使宏更安全一些(通过将 __LINE__ 的值附加到它),但这需要三个宏,并且可能不是必需的:

#if IS_DEBUGGING
// paste symbols 'x' and 'y' together
#  define TMP_DBG_FLAG_SYMCAT0(x,y) x ## y
// need one level of indirection to expand __LINE__...
#  define TMP_DBG_FLAG_SYMCAT(x,y) TMP_DBG_FLAG_SYMCAT0(x,y)
#  define TMP_DBG_FLAG volatile bool TMP_DBG_FLAG_SYMCAT(dbg_flag_,__LINE__) = false
#else
#  define TMP_DBG_FLAG false
#endif