告诉gcc函数调用不会返回

Tom*_*Tom 24 c gcc gcc-warning noreturn

我正在使用C99GCC.

我有一个static inline在头文件中声明的函数,我无法修改.

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

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

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

我已尝试在属性中包含声明,但会收到有关重复声明的警告.

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

Ant*_*ine 37

定义的函数和调用外部函数的函数中,添加一个__builtin_unreachable内置于至少GCCClang编译器的调用并进行标记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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 函数只有两种不返回的方法:永远循环,或者使通常的控制流短路(例如抛出异常,跳出函数,终止进程等)
  • 在某些情况下,编译器可能能够通过静态分析推断并证明函数不会返回.即使在理论上,这并不总是可行的,因为我们希望编译器快速,只能检测到明显/容易的情况.
  • __attribute__((noreturn))是一个注释(如const),这是程序员通知编译器他绝对确定函数不会返回的一种方式.遵循信任但验证原则,编译器试图证明函数确实没有返回.如果它证明函数可能返回则可能发出错误,如果无法证明函数是否返回则发出警告.
  • __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
}
Run Code Online (Sandbox Code Playgroud)


Bas*_*tch 7

几种解决方案

用the重新声明你的功能 __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);
 }
Run Code Online (Sandbox Code Playgroud)

用宏覆盖

最后,您可以定义一个宏

#define func(A) {func(A); __builtin_unreachable();}
Run Code Online (Sandbox Code Playgroud)

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

如果您从未返回func被声明为返回例如int您将使用的语句表达类似

#define func(A) ({func(A); __builtin_unreachable(); (int)0; })
Run Code Online (Sandbox Code Playgroud)

像上面这样的基于宏的解决方案并不总是有效,例如,如果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;
 }
Run Code Online (Sandbox Code Playgroud)

当使用GCC 4.9(来自Debian/Sid/x86-64)编译时,as 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
Run Code Online (Sandbox Code Playgroud)

您可以使用#pragma GCC诊断来有选择地禁用警告.


使用MELT自定义GCC

最后,您可以gcc使用MELT插件自定义最近的并编写简单的扩展名(使用MELT域特定语言),以便noreturn在包含所需功能时添加属性.它可能是十几个MELT行,使用register_finish_decl_first和匹配函数名称.

由于我是MELT(自由软件GPLv3 +)的主要作者,如果您提出要求,我甚至可以为您编写代码,例如此处或最好是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)
Run Code Online (Sandbox Code Playgroud)

真正的MELT代码稍微复杂一些.你想在your_adding_attr_mode那里定义.向我询问更多.

一旦你编码您MELT扩展your_melt_mode.melt 您的需求(和编译融化拓成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
Run Code Online (Sandbox Code Playgroud)

换句话说,你只需要添加一些-fplugin-*标志到CFLAGSMakefile!

顺便说一句,我只是在MELT监视器中进行编码(在github上:https://github.com/bstarynk/melt-monitor ...,提交meltmom-process.melt一些非常相似的文件.

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

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