簡體   English   中英

C++ 標准是否要求 C 鏈接函數是“noexcept”?

[英]Does the C++ standard mandate that C-linkage functions are `noexcept`?

我在標准中找不到任何強制使用extern "C"聲明的函數為noexcept ,無論是隱式還是顯式。

然而,應該清楚的是,C 調用約定不能支持異常……或者是嗎?

標准是否提到了這一點,我錯過了某個地方? 如果沒有,為什么不呢? 它是否只是作為各種實現細節留下?

據我所知,不能保證用“C”鏈接定義的函數不會拋出異常。 該標准允許 C++ 程序調用具有“C”語言鏈接的外部函數,以及定義用 C++ 編寫的具有“C”語言鏈接的函數。 因此,沒有什么可以阻止 C++ 程序調用具有“C”語言鏈接的函數,而該函數實際上是用 C++ 編寫的(可能在另一個編譯單元中,盡管這並不是必需的)。 這樣做會很奇怪,但很難排除。 我也沒有看到標准中的哪里說這樣做會導致未定義的行為(事實上,由於標准無法定義不是用 C++ 編寫的函數的行為,這將是沒有正式未定義行為的唯一用法)。

因此,我認為假設“C”鏈接意味着noexcept是錯誤的。

嗯,我假設extern "C"只使用 C-linkage,而不是 C 函數。 它可以防止編譯器執行C++ 名稱修改

更直接 - 假設這段代碼。

// foo.cpp
extern "C" void foo()
{
    throw 1;
}

// bar.cpp
extern "C" void foo();
void bar()
{
    try
    {
        foo();
    }
    catch (int)
    {
        // yeah!
    }
}

Marc van Leeuwen 的回答是正確的:查找當前的工作草案,似乎沒有任何內容要求聲明extern "C"函數是隱式noexcept 有趣的是,標准 C++ 禁止 C++ 標准庫中的 C 標准庫函數拋出異常。 這些函數本身通常被指定為extern "C" (但這是定義的實現,參見16.4.3.3-2 )。 查看第16.4.6.13 條[對異常處理的限制] 以及隨附的腳注174175

C 標准庫中的函數不應拋出異常 [腳注 174],除非此類函數調用程序提供的拋出異常的函數。 [腳注 175]

腳注 174:

  1. 也就是說,C 庫函數都可以被視為標記為 noexcept。 這允許實現基於運行時不存在異常進行性能優化。

腳注 175:

函數 qsort() 和 bsearch() ([alg.c.library]) 滿足這個條件。

話雖如此,遵循與標准庫相同的策略通常是一個很好的設計指南,出於Marc van Leeuwen 的回答中提到的原因,我認為用戶定義的extern "C"函數也用noexcept指定是個好主意,除非它傳遞一個指向 C++ 函數的指針作為回調參數,如 qsort 等。 我用下面的代碼用clang10、gcc10做了一個小實驗:

#include <cstring>
#include <cstdlib>
#include <iostream>

extern "C" int cmp(const void* lhs, const void* rhs) noexcept;
extern "C" int non_throwing();

int main()
{
    constexpr int src[] = {10, 9, 8, 7, 6, 5};
    constexpr auto sz = sizeof *src;
    constexpr auto count = sizeof src / sz;

    int dest[count];
    int key = 7;

    std::cout << std::boolalpha
    // noexcept is unevaluated so no worries about UB here
        << "non_throwing: " << noexcept(non_throwing()) << '\n'
        << "memcpy: " << noexcept(std::memcpy(dest, src, sizeof dest)) << '\n'
        << "malloc: "<< noexcept(std::malloc(16u)) << '\n'
        << "free: " << noexcept(std::free(dest)) << '\n'
        << "exit: " << noexcept(std::exit(0)) << '\n'
        << "atexit: " << noexcept(std::atexit(nullptr)) << '\n'
        << "qsort: " << noexcept(std::qsort(dest, count, sz, cmp)) << '\n' // should fail
        << "bsearch: " << noexcept(std::bsearch(&key, dest, count, sz, cmp)) << '\n'; // should fail
}

gcc10 和 clang10 的輸出是:

non_throwing: false
memcpy: true
malloc: true
free: true
exit: true
atexit: true
qsort: false
bsearch: false

對於 msvc142,如果使用 /EHsc 編譯,則所有輸出顯然都是true 使用 /EHs,所有輸出都是假的,這使得 /EHsc 中的“c”必須嚴格遵守。

沒有任何地方說extern "C"函數是noexcept 另一方面,幾乎所有的 C 標准庫函數都是noexcept除非你做了一些奇怪的事情。 通常,這歸結為調用未定義的行為,但還有一些其他情況。 這些應該是全部:

  • qsort()的函數指針參數可以拋出; 因此qsort()可以拋出。
  • bsearch()也是如此。
  • 您可以替換malloc()realloc()free() 如果你這樣做,這些可能會拋出。
  • 在前一種情況下, calloc()fopen()fclose()freopen()system()strdup()也可能拋出。 strdup()已定義但不保證存在。)
  • setjmp()catch(...)不要混用。 至少有一個平台將longjmp()實現為throw jmp_buf的邏輯等價物,從而導致catch(...)捕獲它。
  • 未定義的行為可能會拋出。 一些系統實際上確實將 *NULL 實現為拋出異常,即使在編譯 C 代碼時,它也可以被catch(...) 如果您在任何地方執行未定義行為,則一旦代碼路徑不可撤銷地致力於達到未定義行為,整個程序就會未定義,因此這可能導致 C 標准庫函數拋出。

AC 函數foo可以調用一個用 C++ 編碼的函數bar ,聲明為extern "C" ,這樣bar就(可能間接地) throw一些 C++ 異常。

AC 函數foo (可能被某些 C++ 函數調用)可以調用longjmp,其運行時行為接近於異常拋出。

IIRC,第一個 C++ 編譯器 ( Cfront ) 已經使用longjmp生成 C 代碼來翻譯throw (和setjmp用於翻譯catch )。 當然,C++ 析構函數使事情復雜化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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