我在C99
下使用GCC
。
我在标题中声明了static
inline
的函数,我无法修改。
该函数永不返回,但未标记为__attribute__((noreturn))
。
如何以告诉编译器不会返回的方式调用该函数?
我从我自己的noreturn函数调用它,部分想要抑制" noreturn函数返回"警告,但也想帮助优化器等。
我已尝试在属性中加入声明,但会收到有关重复声明的警告。
我尝试创建一个函数指针并将该属性应用于该函数,但它表示函数属性不能应用于指向函数。
答案 0 :(得分:33)
从你定义的函数中调用外部函数,添加对至少GCC和Clang编译器内置的__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
}
编辑:只是为了澄清评论中提出的几点,并且通常会给出一些背景信息:
__attribute__((noreturn))
是一个注释(如const
),这是程序员通知编译器他绝对确定函数不会返回的一种方式。遵循 trust但验证原则,编译器尝试证明该函数确实没有返回。如果证明函数可能返回则可能发出错误,如果无法证明函数是否返回则发出警告。__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
}
答案 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来有选择地禁用警告。
最后,您可以使用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。