C++ 标准是否要求 C 链接函数是“noexcept”的?

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

提问人:Lightness Races in Orbit 提问时间:6/23/2014 最后编辑:Lightness Races in Orbit 更新时间:2/4/2021 访问量:2517

问:

我在标准中找不到任何强制函数声明为 ,无论是隐式还是显式。extern "C"noexcept

然而,应该清楚的是,C 调用约定不能支持异常......或者是吗?

标准是否在我错过的地方提到了这一点?如果不是,为什么不呢?它是否只是作为某种实现细节而保留?

C++ C C++11 语言-律师 noexcept

评论

0赞 Sheen 6/23/2014
无法想象这种规模的兼容性在C++语言的演变中会被打破,不是吗?
2赞 Puppy 6/23/2014
至于它是否会破坏兼容性,这是非常值得怀疑的。从 C 函数泄漏异常的程序可能总是具有未定义的行为。
4赞 ta.speot.is 6/23/2014
相关 stackoverflow.com/a/15845731/242520
0赞 Lightness Races in Orbit 6/23/2014
@ta.speot.is 实际上可能是骗子-.-
1赞 Hans Passant 6/23/2014
MSVC++ 编译器似乎认为它未指定,即 /EHs 与 /EHsc。

答:

9赞 ikh 6/23/2014 #1

嗯,我假设只使用 C 链接,而不是 C 功能。它可以防止编译器执行 C++ 名称修改extern "C"

更直接地说 - 假设此代码。

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

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

评论

1赞 Captain Obvlious 6/25/2014
实际上,它不仅仅是防止名称篡改。 与 不同。它们是不同的类型,具有不同的属性,链接只是其中之一。extern "C++" void Foo();extern "C" void Foo();
1赞 Lightness Races in Orbit 3/30/2016
我认为这也会影响调用约定,并且异常需要特定的调用约定才能使堆栈展开正常工作。
22赞 Marc van Leeuwen 6/23/2014 #2

据我所知,不能保证用“C”链接定义的函数不会抛出异常。该标准允许 C++ 程序调用具有“C”语言链接的外部函数,并定义用 C++ 编写的具有“C”语言链接的函数。因此,没有什么可以阻止 C++ 程序调用具有“C”语言链接的函数,该函数实际上是用 C++ 编写的(也许在另一个编译单元中,尽管这不是必需的)。这将是一件奇怪的事情,但很难排除。此外,我看不出标准中的哪个地方说这样做会导致未定义的行为(事实上,由于标准无法定义不是用 C++ 编写的函数的行为,这将是没有正式未定义行为的唯一用法)。

因此,我认为假设“C”链接意味着 是错误的。noexcept

评论

1赞 didierc 6/23/2014
好的答案,你当然知道如何以正确的方式表达问题。
4赞 Tavian Barnes 6/24/2014
一个不太奇怪的情况是接受(并调用)函数指针的 C 函数,该指针可能指向抛出的 C++ 函数。
0赞 Joshua 5/23/2019 #3

没有任何地方说函数是 .另一方面,几乎所有的 C 标准库函数都是,除非你做了一些奇怪的事情。通常,这归结为调用未定义的行为,但还有其他一些情况。这些应该是所有这些:extern "C"noexceptnoexcept

  • 函数指针参数 to can throw;因此可以抛出。qsort()qsort()
  • 这同样适用于 。bsearch()
  • 您可以替换 、 和 。如果你这样做,这些可能会扔掉。malloc()realloc()free()
  • 在前一种情况下,、、、(已定义,但不保证存在。calloc()fopen()fclose()freopen()system()strdup()strdup()
  • setjmp()不要混在一起。至少有一个平台被实现为 的逻辑等价物,导致捕获它。catch(...)longjmp()throw jmp_bufcatch(...)
  • 未定义的行为可能会引发。一些系统实际上确实将 *NULL 实现为抛出异常,即使在编译 C 代码时也可以捕获该异常。如果在任何地方执行未定义的行为,则一旦代码路径不可撤销地提交到未定义的行为,整个程序就会处于未定义状态,因此这可能会导致抛出 C 标准库函数。catch(...)

评论

0赞 supercat 5/23/2019
是否在比较函数传递给或引发异常时定义行为?某些平台的异常处理机制仅在 和 之间的每个堆栈帧都具有特定布局时才有效,但当不需要堆栈展开时,其他布局可能更有效,并且此类平台的 C 编译器通常没有理由不使用此类布局。qsortbsearchthrowcatch
0赞 Joshua 5/23/2019
@supercat:我见过的每个具有该属性的拱门都会盲目地展开任何中间堆栈帧,因为展开会继续向下,直到它识别出下一帧。这可能会造成麻烦,但我无法想象它会造成麻烦。此外,我希望在这不起作用的架构上,当从 C++ 包含时会声明指向的函数指针。他们已经不得不使用 a 来使链接工作,所以这不会有什么麻烦。fopen()qsort()qsort()noexcep#ifdef
0赞 Joshua 5/23/2019
@supercat:另一方面,如果你的断言是正确的,那么传递给自定义 C 函数的任何函数指针都必须因此使所有 C 函数与这个问题的所有答案相反。noexceptnoexcept
0赞 supercat 5/23/2019
要求不是函数是 ,而是它们不会在直接从外部语言函数调用的任何情况下抛出/泄漏异常。在 x86 上,函数访问其参数的最简单方法是以 开头,然后在 exit 时开始。如果所有函数都这样做,它将创建一个堆栈帧的链接列表,但并不要求所有函数都这样做。接收一个参数的函数可以以 ...noexceptpush ebp / mov ebp,espmov esp,ebp / pop ebppop edx / pop eax / push eax / push edx
0赞 supercat 5/23/2019
然后以一个简单的 if 它只需要使用一次参数结束。或者,它可以跟踪 ESP 的当前值和起始值之间的关系,如果堆栈比函数启动时深字节,则使用寻址模式。通过执行此类操作的函数进行堆栈展开可能是不可能的。ret[ESP+(n+8)]n
3赞 Julien Villemure-Fréchette 2/4/2021 #4

Marc van Leeuwen 的回答是正确的:查看当前的工作草案,似乎没有任何内容要求声明的职能是隐含的。有趣的是,标准 C++ 禁止抛出 C++ 标准库中的 C 标准库函数。这些函数本身通常被指定为(但这是定义的实现,参见 16.4.3.3-2)。请看第16.4.6.13条[对例外处理的限制]和随附的脚注174和175extern "C"noexceptextern "C"

C 标准库中的函数不应引发异常 [脚注 174],除非此类函数调用引发异常的程序提供的函数。[脚注175]

脚注174:

  1. 也就是说,C 库函数都可以被视为标记为 noexcept。 这允许实现在运行时不存在异常的基础上进行性能优化。

脚注175:

函数 qsort() 和 bsearch() ([alg.c.library]) 满足此条件。

话虽如此,遵循与标准库相同的策略通常是一个很好的设计指南,并且由于 Marc van Leeuwen 的回答中提到的原因,我认为用户定义的函数也指定是一个好主意,除非它被传递给指向 C++ 函数的指针作为回调参数,如 qsort 等。我用 clang10、gcc10 做了一个小实验,代码如下:extern "C"noexcept

#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 编译,则所有输出显然都是 .使用 /EH 时,所有输出都是假的,这使得 /EHsc 中的“c”对于严格一致性是必需的。true

0赞 Basile Starynkevitch 2/4/2021 #5

C 函数可以调用用 C++ 编码的函数,声明 ,例如(可能间接地)-ing 某些 C++ 异常。foobarextern "C"barthrow

C 函数(可能由某些 C++ 函数调用)可以调用运行时行为接近于异常抛出的 longjmpfoo

IIRC,第一个 C++ 编译器 (Cfront) 生成了用于翻译(和翻译)的 C 代码。当然,C++ 析构函数使事情复杂化。longjmpthrowsetjmpcatch