我C99
在GCC
.
我static
inline
在无法修改的标头中声明了一个函数。
该函数永远不会返回但未标记__attribute__((noreturn))
。
如何以告诉编译器它不会返回的方式调用该函数?
我从我自己的 noreturn 函数中调用它,部分想抑制“noreturn 函数返回”警告,但也想帮助优化器等。
我尝试包含一个带有属性的声明,但收到有关重复声明的警告。
我试过创建一个函数指针并将属性应用到那个,但它说函数属性不能应用于指向的函数。
我C99
在GCC
.
我static
inline
在无法修改的标头中声明了一个函数。
该函数永远不会返回但未标记__attribute__((noreturn))
。
如何以告诉编译器它不会返回的方式调用该函数?
我从我自己的 noreturn 函数中调用它,部分想抑制“noreturn 函数返回”警告,但也想帮助优化器等。
我尝试包含一个带有属性的声明,但收到有关重复声明的警告。
我试过创建一个函数指针并将属性应用到那个,但它说函数属性不能应用于指向的函数。
从您定义的函数以及调用外部函数的函数中,添加一个调用,__builtin_unreachable
该调用至少内置于GCC和Clang编译器中并标记为noreturn
. 事实上,这个函数没有做其他任何事情,也不应该被调用。它只是在这里,以便编译器可以推断程序执行将在此时停止。
static inline external_function() // lacks the noreturn attribute
{ /* does not return */ }
__attribute__((noreturn)) void your_function() {
external_function(); // the compiler thinks execution may continue ...
__builtin_unreachable(); // ... and now it knows it won't go beyond here
}
编辑:只是为了澄清评论中提出的几点,并通常提供一些背景信息:
__attribute__((noreturn))
是一个注解(如const
),它是程序员通知编译器他绝对确定函数不会返回的一种方式。遵循信任但验证原则,编译器试图证明该函数确实没有返回。如果它证明函数可以返回,则如果它可能会发出错误,或者如果它无法证明函数是否返回,则可能会发出警告。__builtin_unreachable
具有未定义的行为,因为它不应该被调用。它只是为了帮助编译器的静态分析。事实上,编译器知道这个函数不会返回,所以后面的任何代码都被证明是无法访问的(除非通过跳转)。一旦编译器(通过它自己或在程序员的帮助下)确定某些代码是不可访问的,它可能会使用这些信息来进行如下优化:
__builtin_unreachable()
无法访问以下代码。noreturn
。这就是发生的事情your_function
。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
}
几种解决方案:
__attribute__
您应该尝试通过添加来修改其标题中的该函数__attribute__((noreturn))
。
您可以使用新属性重新声明一些函数,正如这个愚蠢的测试所展示的(向 中添加属性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
,您将使用如下语句表达式
#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) 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
您可以使用#pragma GCC diagnostic来选择性地禁用警告。
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)
真正的 MELT 代码稍微复杂一些。你想在your_adding_attr_mode
那里定义。问我更多。
一旦您your_melt_mode.melt
根据需要编写了 MELT 扩展(并将 MELT 扩展编译your_melt_mode.quicklybuilt.so
为MELT 教程中记录的内容),您将使用
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
在Makefile
!
顺便说一句,我只是在 MELT 监视器中编码(在 github 上:https ://github.com/bstarynk/melt-monitor ...,文件meltmom-process.melt
非常相似。
使用 MELT 扩展,您不会收到任何额外的警告,因为 MELT 扩展会动态更改声明函数的内部 GCC AST(一个 GCC树)!
使用 MELT 定制 GCC 可能是最安全的解决方案,因为它正在修改 GCC 内部 AST。当然,它可能是最昂贵的解决方案(它是 GCC 特定的,并且可能需要在 GCC 发展时进行小改动,例如在使用 GCC 的下一个版本时),但正如我试图展示的那样,它在你的情况。
PS。2019 年,GCC MELT 是一个废弃的项目。如果您想定制 GCC(对于任何最新版本的 GCC,例如 GCC 7、8 或 9),您需要用 C++ 编写自己的GCC 插件。