[英]Tell gcc that a function call will not return
我在GCC
下使用C99
。
我在無法修改的標頭中聲明了一個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
發生的your_function
。pure
函數)的計算都可以刪除。插圖:
external_function
的調用,因為它可能有副作用。 事實上,它可能至少有終止進程的副作用!your_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) gcc -S -fverbose-asm -O2 ex.c
為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 診斷來選擇性地禁用警告。
最后,您可以使用MELT插件自定義您最近的gcc
,並編碼您的簡單擴展(使用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
。 問我更多。
一旦你編碼您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
換句話說,您只需在Makefile
的CFLAGS
中添加一些-fplugin-*
標志即可!
順便說一句,我只是在 MELT 監視器中編碼(在 github 上: https : //github.com/bstarynk/melt-monitor ...,文件meltmom-process.melt
非常相似。
使用 MELT 擴展,您不會收到任何額外的警告,因為 MELT 擴展會動態更改聲明函數的內部 GCC AST(GCC樹)!
使用 MELT 自定義 GCC 可能是最防彈的解決方案,因為它正在修改 GCC 內部 AST。 當然,它可能是最昂貴的解決方案(並且它是特定於 GCC 的,並且在 GCC 發展時可能需要-小-更改,例如在使用 GCC 的下一個版本時),但正如我試圖表明它在你的情況。
附注。 2019 年,GCC MELT 是一個廢棄的項目。 如果您想自定義 GCC(對於任何最新版本的 GCC,例如 GCC 7、8 或 9),您需要用 C++ 編寫您自己的GCC 插件。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.