如何提示GCC在编译时某行代码应该是不可达的?

24

编译器通常会提供开关以警告代码不可达。我还见过一些库的宏,提供对于不可达代码的断言

是否有提示,例如通过pragma或内置项,可以传递给GCC(或任何其他编译器),在编译期间警告或错误,如果确定某行预期为不可达,则可以到达?

这里是一个示例:

    if (!conf->devpath) {
        conf->devpath = arg;
        return 0;
    } // pass other opts into fuse
    else {
        return 1;
    }
    UNREACHABLE_LINE();

这个的价值在于检测,在预期无法到达的线路上方条件发生变化后,该线路实际上是可达的。

4个回答

26

gcc 4.5支持__builtin_unreachable()编译器内联,结合-Wunreachable-code可能会达到你想要的效果,但很可能会导致虚假警告。


3
请注意,gcc的较新版本中删除了“-Wunreachable-code”选项(因为在不同的优化选项下表现极其不可预测)。 - Hasturkun
1
__builtin_unreachable() 似乎不能实现 OP 想要的功能,即在可达到的情况下在编译时中断。如果没有使用 -fsanitize=unreachable,即使代码在编译时被触发,它也不会默认断言,而是 UB。请参见:https://dev59.com/A3A75IYBdhLWcg3wSm64#52722868 - Ciro Santilli OurBigBook.com
啊,好的,现在我明白了,它以前会在__builtin_unreachable()之后有代码时发出警告。但是对于大多数代码,你通常会在这种情况下使用return,对吧? - Ciro Santilli OurBigBook.com

10

__builtin_unreachable()在GCC 7.3.0上没有生成任何编译时警告

我在文档中也没有找到任何关于此的说明。

例如,以下示例将编译而无需任何警告:

#include <stdio.h>

int main(void) {
    __builtin_unreachable();
    puts("hello")
    return 0;
}

使用:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -Wunreachable-code main.c

我认为它唯一能做到的是,让编译器根据某行代码永远不被执行这个事实来进行某些优化,并在您出现编程错误并且该行代码确实被执行时给出未定义行为。

例如,执行上面的示例似乎正常退出,但没有像预期的那样打印 hello。我们的汇编分析随后显示,正常看起来的退出实际上只是一个偶然的未定义行为。

GCC 的 -fsanitize=unreachable 标志将 __builtin_unreachable(); 转换为在运行时失败的断言:

<stdin>:1:17: runtime error: execution reached a __builtin_unreachable() call

然而在Ubuntu 16.04中,该标志已经失效:ld: unrecognized option '--push-state--no-as-needed'

__builtin_unreachable()对可执行文件有什么影响?

如果我们使用__builtin_unreachable来反汇编代码:

objdump -S a.out

我们可以看到没有它的时候调用了puts函数:
000000000000063a <main>:
#include <stdio.h>

int main(void) {
 63a:   55                      push   %rbp
 63b:   48 89 e5                mov    %rsp,%rbp
    puts("hello");
 63e:   48 8d 3d 9f 00 00 00    lea    0x9f(%rip),%rdi        # 6e4 <_IO_stdin_used+0x4>
 645:   e8 c6 fe ff ff          callq  510 <puts@plt>
    return 0;
 64a:   b8 00 00 00 00          mov    $0x0,%eax
}
 64f:   5d                      pop    %rbp
 650:   c3                      retq
 651:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 658:   00 00 00
 65b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

只有带有它的那个执行:

而没有它的那个不执行:

int main(void) {
 5fa:   55                      push   %rbp
 5fb:   48 89 e5                mov    %rsp,%rbp
 5fe:   66 90                   xchg   %ax,%ax

甚至没有返回,所以我认为它只是一种未定义的行为巧合,它没有崩溃。

为什么GCC不能确定某些代码是否不可达?

我收集了以下答案:

  • determining unreachable code automatically is too hard for GCC for some reason, which is why for years now -Wunreachable-code does nothing: gcc does not warn for unreachable code

  • users may use inline assembly that implies unreachability, but GCC cannot determine that. This is mentioned on the GCC manual:

    One such case is immediately following an asm statement that either never terminates, or one that transfers control elsewhere and never returns. In this example, without the __builtin_unreachable, GCC issues a warning that control reaches the end of a non-void function. It also generates code to return after the asm.

    int f (int c, int v)
    {
      if (c)
        {
          return v;
        }
      else
        {
          asm("jmp error_handler");
          __builtin_unreachable ();
        }
    }
    

在GCC 7.3.0和Ubuntu 18.04上进行了测试。


6

使用gcc 4.4.0 Windows交叉编译器到PowerPC进行编译,使用-O2或-O3参数,以下代码对我有效:

#define unreachable asm("unreachable\n")

如果编译器未对其进行优化并认为它是无法到达的,汇编程序将因未知操作而失败。

是的,它在不同的优化选项下可能会变得高度不可预测,并且可能在我最终更新编译器时出现故障,但目前它比没有好。


虽然这样可以工作,但无法获取精确的堆栈跟踪。 - user202729
1
这个问题是关于在编译时检测此类代码。因此,不可能有堆栈跟踪。 - Roland Illig

2
如果你的编译器没有你所需的警告,可以通过静态分析器进行补充。我所提到的分析器将具有自己的注释语言和/或识别C中的assert,并使用这些提示在执行特定点时应该为真的属性。如果没有针对无法访问语句的特定注释,你可能可以使用assert(false);
我个人不熟悉这些分析器,但Klokwork和CodeSonar是两个著名的分析器。Goanna是第三种。

2
旧的 lint 工具曾经允许使用注释 /*NOTREACHED*/,这表示代码不应该被执行到... 不确定当工具遇到该注释时的行为如何。 - Aidan Cully

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