告诉gcc函数调用不会返回

时间:2014-08-20 15:31:56

标签: c gcc gcc-warning noreturn

我在C99下使用GCC

我在标题中声明了static inline的函数,我无法修改。

该函数永不返回,但未标记为__attribute__((noreturn))

如何以告诉编译器不会返回的方式调用该函数?

我从我自己的noreturn函数调用它,部分想要抑制" noreturn函数返回"警告,但也想帮助优化器等。

我已尝试在属性中加入声明,但会收到有关重复声明的警告。

我尝试创建一个函数指针并将该属性应用于该函数,但它表示函数属性不能应用于指向函数。

2 个答案:

答案 0 :(得分:33)

定义的函数中调用外部函数,添加对至少GCCClang编译器内置的__builtin_unreachable的调用并标记为noreturn。实际上,这个函数什么都不做,不应该调用。它只在这里,以便编译器可以推断程序执行将在此时停止。

static inline external_function() // lacks the noreturn attribute
{ /* does not return */ }

void your_function() __attribute__((noreturn)) {
    external_function();     // the compiler thinks execution may continue ...
    __builtin_unreachable(); // ... and now it knows it won't go beyond here
}

编辑:只是为了澄清评论中提出的几点,并且通常会给出一些背景信息:

  • 一个函数只有两种不返回的方法:永远循环,或者使通常的控制流短路(例如抛出异常,跳出函数,终止进程等)。
  • 在某些情况下,编译器可能能够通过静态分析推断并证明函数不会返回。即使在理论上,这也是not always possible,因为我们希望编译器快速 ,只检测到明显/简单的情况。
  • __attribute__((noreturn))是一个注释(如const),这是程序员通知编译器他绝对确定函数不会返回的一种方式。遵循 trust但验证原则,编译器尝试证明该函数确实没有返回。如果证明函数可能返回则可能发出错误,如果无法证明函数是否返回则发出警告。
  • __builtin_unreachable具有未定义的行为,因为它不应被调用。它只是为了帮助编译器的静态分析。实际上编译器知道这个函数没有返回,因此任何下面的代码都可以证明是不可达的(除了通过跳转)。

一旦编译器(无论是单独使用,还是程序员的帮助)建立了某些代码无法访问的程序,它就可以使用这些信息进行这样的优化:

  • 如果函数永不返回
  • ,则删除用于从函数返回到其调用者的样板代码
  • 传播不可达性信息,即如果代码点的唯一执行路径是通过无法访问的代码,则此点也无法访问。例子:
    • 如果函数未返回,则跟随其调用且无法通过跳转访问的任何代码也无法访问。示例:__builtin_unreachable()之后的代码无法访问。
    • 特别是,函数返回的唯一途径是通过无法访问的代码,该函数可以标记为noreturn。那就是your_function
    • 会发生什么
    • 不需要在无法访问的代码中使用的任何内存位置/变量,因此不需要设置/计算此类数据的内容。
    • 任何可能(1)不必要的计算(上一个项目符号)和(2)没有任何副作用(例如pure函数)可能是除去。

插图:   - 无法删除对external_function的调用,因为它可能有副作用。事实上,它可能至少有终止过程的副作用!   - 可以移除your_function的返回锅炉板

此处的另一个示例显示了如何删除之前无法访问的点

int compute(int) __attribute((pure)) { return /* expensive compute */ }
if(condition) {
    int x = compute(input); // (1) no side effect => keep if x is used
                            // (8) x is not used  => remove
    printf("hello ");       // (2) reachable + side effect => keep
    your_function();        // (3) reachable + side effect => keep
                            // (4) unreachable beyond this point
    printf("word!\n");      // (5) unreachable => remove
    printf("%d\n", x);      // (6) unreachable => remove
                            // (7) mark 'x' as unused
} else {
                            // follows unreachable code, but can jump here
                            // from reachable code, so this is reachable
   do_stuff();              // keep
}

答案 1 :(得分:7)

几种解决方案:

使用__attribute__

重新声明您的功能

您应该尝试通过向其添加__attribute__((noreturn))来修改其标题中的该功能。

您可以使用new属性重新声明某些函数,因为这个愚蠢的测试演示了(向fopen添加属性):

 #include <stdio.h>

 extern FILE *fopen (const char *__restrict __filename,
            const char *__restrict __modes)
   __attribute__ ((warning ("fopen is used")));

 void
 show_map_without_care (void)
 {
   FILE *f = fopen ("/proc/self/maps", "r");
   do
     {
       char lin[64];
       fgets (lin, sizeof (lin), f);
       fputs (lin, stdout);
     }
   while (!feof (f));
   fclose (f);
 }

覆盖宏

最后,您可以定义一个像

这样的宏
#define func(A) {func(A); __builtin_unreachable();}

(这使用了一个事实,即在宏内部,宏名称不是宏扩展的。)

如果您永不归还的func宣布退回,例如int您将使用statement expression之类的

#define func(A) ({func(A); __builtin_unreachable(); (int)0; })

如上所述的基于宏的解决方案不会一直有效,例如:如果func作为函数指针传递,或者只是某些人编码(func)(1)这是合法但丑陋的。


使用noreturn属性

重新声明静态内联

以下示例:

 // file ex.c
 // declare exit without any standard header
 void exit (int);

 // define myexit as a static inline
 static inline void
 myexit (int c)
 {
   exit (c);
 }

 // redeclare it as notreturn
 static inline void myexit (int c) __attribute__ ((noreturn));

 int
 foo (int *p)
 {
   if (!p)
     myexit (1);
   if (p)
     return *p + 2;
   return 0;
 }

使用GCC 4.9(来自Debian / Sid / x86-64)编译为gcc -S -fverbose-asm -O2 ex.c)时,会给出一个包含预期优化的汇编文件:

         .type   foo, @function
 foo:
 .LFB1:
    .cfi_startproc
    testq   %rdi, %rdi      # p
    je      .L5     #,
    movl    (%rdi), %eax    # *p_2(D), *p_2(D)
    addl    $2, %eax        #, D.1768
    ret
.L5:
    pushq   %rax    #
    .cfi_def_cfa_offset 16
    movb    $1, %dil        #,
    call    exit    #
    .cfi_endproc
 .LFE1:
    .size   foo, .-foo

您可以使用#pragma GCC diagnostic来有选择地禁用警告。


使用GCC

自定义MELT

最后,您可以使用MELT插件自定义最近的gcc并对简单扩展名进行编码(使用 MELT 域特定语言)以添加属性{{1当包含所需的功能时。它可能是十几条MELT行,使用noreturn并在函数名称上匹配。

由于我是 MELT (自由软件GPLv3 +)的主要作者,如果您提出要求,我甚至可以为您编写代码,例如:在这里或最好在register_finish_decl_first;给出你永不归还的功能的具体名称。

可能MELT代码看起来像:

gcc-melt@googlegroups.com

真正的MELT代码稍微复杂一些。您想在那里定义 ;;file your_melt_mode.melt (module_is_gpl_compatible "GPLv3+") (defun my_finish_decl (decl) (let ( (tdecl (unbox :tree decl)) ) (match tdecl (?(tree_function_decl_named ?(tree_identifier ?(cstring_same "your_function_name"))) ;;; code to add the noreturn attribute ;;; .... )))) (register_finish_decl_first my_finish_decl) 。向我询问更多信息。

根据您的需要编写MELT扩展your_adding_attr_mode(并在MELT教程中将MELT扩展编译为your_melt_mode.melt作为documented),您将使用<编译代码< / p>

your_melt_mode.quicklybuilt.so

换句话说,您只需在 gcc -fplugin=melt \ -fplugin-arg-melt-extra=your_melt_mode.quicklybuilt \ -fplugin-arg-melt-mode=your_adding_attr_mode \ -O2 -I/your/include -c yourfile.c 中的-fplugin-*添加几个CFLAGS标记!

BTW,我只是在MELT监视器中编码(在github上:https://github.com/bstarynk/melt-monitor ...,文件Makefile非常相似。

使用MELT扩展,您不会收到任何其他警告,因为MELT扩展会动态改变声明函数的内部GCC AST(GCC Tree )! / p>

使用MELT自定义GCC可能是最具防弹性的解决方案,因为它正在修改GCC内部AST。当然,它可能是最昂贵的解决方案(并且它是GCC特定的,并且可能需要 - 当GCC正在发展时变小,例如当使用下一版本的GCC时),但是因为我试图证明它很容易你的情况。

PS。 2019年,GCC MELT是一个废弃的项目。如果要自定义GCC(对于任何最新版本的GCC,例如GCC 7,8或9),您需要在C ++中编写自己的GCC plugin