【问题标题】:Emulating GCC's __builtin_unreachable?模拟 GCC 的 __builtin_unreachable?
【发布时间】:2011-05-17 13:52:40
【问题描述】:

我收到了很多关于开关的警告,这些警告仅部分覆盖了切换的枚举范围。因此,我想为所有这些开关设置一个“默认值”,并在这种情况下放置__builtin_unreachable(GCC 内置),以便编译器知道这种情况是不可达的。

但是,我知道 GCC4.3 还不支持该内置函数。有什么好方法可以模拟该功能吗?我考虑过取消引用空指针,但这可能会产生其他不良影响/警告等。你有更好的主意吗?

【问题讨论】:

  • switch((int)myenum) /*silence warnings*/ ^_^

标签: c++ gcc unreachable-code


【解决方案1】:

您可以调用声明为_Noreturn 的内联函数,以将该调用之后的任何内容标记为无法访问。允许编译器在这样的函数之后抛出任何代码。如果函数本身是static(并且确实返回),编译器通常也会内联函数。这是一个例子:

static _Noreturn void unreachable() {
    return; /* intentional */
}

/* ... */

foo();
bar(); /* should better not return */
unreachable();
baz(); /* compiler will know this is not reachable */

请注意,如果标记为 _Noreturn 的函数确实返回,则您将调用未定义的行为。确保永远不会调用该函数。

【讨论】:

  • 它通过说“功能标记为 noreturn 返回”来发出警告。
  • @Johannes Schaub 没错。 gcc 会发出警告但接受代码。只要您的代码从未真正达到 unreachable(),您的代码就不应表现出未定义的行为。
  • 这是 __builtin_unreachable 的正确基础,它按原样演变为内置。禁用特定警告,因为它是有意返回的。除了返回之外,可能还有一些其他的技巧,但是这个原样等同于 __builtin_unreachable。比较 ideone.com/f8yrLXideone.com/VP6NOB
  • @FUZxxl 你不应该调用 abort() 而不是 return。它不仅不会导致未定义的行为,而且是正确的,因为 abort 函数是使用 _Noreturn 函数说明符定义的。
  • @2501 __builtin_unreachable() 的想法是给编译器一个它不能自行干预的提示。通过插入对abort() 的调用无法实现此目标。 __builtin_unreachable() 是关于优化,而不是关于安全。
【解决方案2】:

嗯,类似(因为 __builtin_unreachable() 出现在 4.5 中):

#define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) #if GCC_VERSION >= 40500 #define my_unreachable() __builtin_unreachable() #else #define my_unreachable() do { printf("Oh noes!!!111\n"); abort(); } while(0) #endif

【讨论】:

  • if (x) y(); else my_unreachable(); 不适用于您的定义
  • 但这仍然假设代码是可访问的。不允许编译器优化该 printf 并中止,因此它必须发出该代码。我想特别告诉编译器宏被放入的地方是不可到达的,这样它就省略了代码生成或对它之后的所有控制流点的警告。
  • @litb:实际上,您是在问“我的编译器不允许我将代码指定为不可访问,我如何告诉我的编译器代码不可访问”。真的不会计算。无论如何,至少在我的系统 abort() 上标有 attribute ((noreturn)) ,因此编译器应该能够摆脱该调用下方的任何代码。我猜你仍然会收到警告。
  • @janneb 不,我正在寻找一种解决方法来实现与__builtin_unreachable 类似的事情。调用“abort”不会这样做,因为它已经定义了行为,所以编译器仍然必须在那个地方调用“abort”:(
  • @nos 会影响性能。这个地方已经被证明是一个瓶颈,所以我需要避免编译器发出的任何无用的分支和样板,因为它认为某些代码是可以访问的(如果逻辑证明它无法访问,可能是基于更高级别的逻辑,通过纯粹静态分析代码是不可见的)。
【解决方案3】:

abort(留下核心转储)或throw(允许备用数据捕获)是否能满足您的需求?

您真的想要有不涵盖完整枚举的 switch 语句吗?我几乎总是尝试在没有默认情况的情况下列出所有可能的情况(到无操作),这样如果添加了新的枚举,gcc 会警告我,因为可能需要处理它们而不是让它静默(在编译期间)下降进入默认值。

【讨论】:

    【解决方案4】:

    保持简单:

    assert(false);
    

    或者,更好的是:

    #define UNREACHABLE (!"Unreachable code executed!")
    
    assert(UNREACHABLE);
    

    【讨论】:

    • 太简单了。编译器仍会将断言之后的任何内容视为可访问,这可能会导致不需要的警告。
    • 你确定吗? POSIX 说 assert() 是一个宏,编译器应该能够静态地看到 false 永远不会评估为 true,因此最终将 assert(false) 转换为对 abort() 的调用...
    • @CAFxX 仅在未定义 NDEBUG 时才成立。断言在非调试版本中是无操作的。
    【解决方案5】:
    template<unsigned int LINE> class Unreachable_At_Line {}; 
    #define __builtin_unreachable() throw Unreachable_At_Line<__LINE__>()
    

    编辑

    由于您希望编译器省略无法访问的代码,下面是最简单的方法。

    #define __builtin_unreachable() { struct X {X& operator=(const X&); } x; x=x; }
    

    编译器会优化掉x = x; 指令,尤其是在它无法访问时。用法如下:

    int foo (int i)
    {
      switch(i)
      {
      case 0:  return 0;
      case 1:  return 1;
      default: return -1;
      }
      __builtin_unreachable();  // never executed; so compiler optimizes away
    }
    

    如果您将__builtin_unreachable() 放在foo() 的开头,则编译器会为未实现的operator = 生成链接器错误。我在 gcc 3.4.6(64 位)中运行了这些测试。

    【讨论】:

    • LINE 将始终扩展到 __builtin_unreachable() 被定义而不是使用的行
    • 这与@janneb 的解决方案有相同的问题。编译器需要发出 throw。
    • @Johannes,你能检查一下编辑后的答案吗?在这里,编译器要么给出错误,要么优化无法访问的部分。所以没有实时并发症。
    猜你喜欢
    • 2020-07-03
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-26
    • 2018-02-19
    相关资源
    最近更新 更多