簡體   English   中英

告訴 gcc 函數調用不會返回

[英]Tell gcc that a function call will not return

我在GCC下使用C99

我在無法修改的標頭中聲明了一個static inline函數。

該函數從不返回但未標記為__attribute__((noreturn))

如何以告訴編譯器它不會返回的方式調用該函數?

我從我自己的 noreturn 函數中調用它,部分是想抑制“noreturn 函數返回”警告,但也想幫助優化器等。

我嘗試在屬性中包含聲明,但收到有關重復聲明的警告。

我曾嘗試創建一個函數指針並將屬性應用於它,但它說函數屬性不能應用於指向的函數。

定義並調用外部函數的函數中,添加對__builtin_unreachable的調用,該調用至少內置於GCCClang編譯器中,並標記為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
    • 不需要任何僅在無法訪問的代碼中使用的內存位置/變量,因此不需要設置/計算此類數據的內容。
    • 任何可能(1)不必要的(先前的項目符號)和(2)沒有副作用(例如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.cgcc -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插件自定義您最近的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

換句話說,您只需在MakefileCFLAGS中添加一些-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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM