提问人:Lightness Races in Orbit 提问时间:6/23/2014 最后编辑:Lightness Races in Orbit 更新时间:2/4/2021 访问量:2517
C++ 标准是否要求 C 链接函数是“noexcept”的?
Does the C++ standard mandate that C-linkage functions are `noexcept`?
问:
我在标准中找不到任何强制函数声明为 ,无论是隐式还是显式。extern "C"
noexcept
然而,应该清楚的是,C 调用约定不能支持异常......或者是吗?
标准是否在我错过的地方提到了这一点?如果不是,为什么不呢?它是否只是作为某种实现细节而保留?
答:
嗯,我假设只使用 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!
}
}
评论
extern "C++" void Foo();
extern "C" void Foo();
据我所知,不能保证用“C”链接定义的函数不会抛出异常。该标准允许 C++ 程序调用具有“C”语言链接的外部函数,并定义用 C++ 编写的具有“C”语言链接的函数。因此,没有什么可以阻止 C++ 程序调用具有“C”语言链接的函数,该函数实际上是用 C++ 编写的(也许在另一个编译单元中,尽管这不是必需的)。这将是一件奇怪的事情,但很难排除。此外,我看不出标准中的哪个地方说这样做会导致未定义的行为(事实上,由于标准无法定义不是用 C++ 编写的函数的行为,这将是没有正式未定义行为的唯一用法)。
因此,我认为假设“C”链接意味着 是错误的。noexcept
评论
没有任何地方说函数是 .另一方面,几乎所有的 C 标准库函数都是,除非你做了一些奇怪的事情。通常,这归结为调用未定义的行为,但还有其他一些情况。这些应该是所有这些:extern "C"
noexcept
noexcept
- 函数指针参数 to can throw;因此可以抛出。
qsort()
qsort()
- 这同样适用于 。
bsearch()
- 您可以替换 、 和 。如果你这样做,这些可能会扔掉。
malloc()
realloc()
free()
- 在前一种情况下,、、、(已定义,但不保证存在。
calloc()
fopen()
fclose()
freopen()
system()
strdup()
strdup()
setjmp()
不要混在一起。至少有一个平台被实现为 的逻辑等价物,导致捕获它。catch(...)
longjmp()
throw jmp_buf
catch(...)
- 未定义的行为可能会引发。一些系统实际上确实将 *NULL 实现为抛出异常,即使在编译 C 代码时也可以捕获该异常。如果在任何地方执行未定义的行为,则一旦代码路径不可撤销地提交到未定义的行为,整个程序就会处于未定义状态,因此这可能会导致抛出 C 标准库函数。
catch(...)
评论
qsort
bsearch
throw
catch
fopen()
qsort()
qsort()
noexcep
#ifdef
noexcept
noexcept
noexcept
push ebp / mov ebp,esp
mov esp,ebp / pop ebp
pop edx / pop eax / push eax / push edx
ret
[ESP+(n+8)]
n
Marc van Leeuwen 的回答是正确的:查看当前的工作草案,似乎没有任何内容要求声明的职能是隐含的。有趣的是,标准 C++ 禁止抛出 C++ 标准库中的 C 标准库函数。这些函数本身通常被指定为(但这是定义的实现,参见 16.4.3.3-2)。请看第16.4.6.13条[对例外处理的限制]和随附的脚注174和175。extern "C"
noexcept
extern "C"
C 标准库中的函数不应引发异常 [脚注 174],除非此类函数调用引发异常的程序提供的函数。[脚注175]
脚注174:
- 也就是说,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
C 函数可以调用用 C++ 编码的函数,声明 ,例如(可能间接地)-ing 某些 C++ 异常。foo
bar
extern "C"
bar
throw
C 函数(可能由某些 C++ 函数调用)可以调用运行时行为接近于异常抛出的 longjmp。foo
IIRC,第一个 C++ 编译器 (Cfront) 生成了用于翻译(和翻译)的 C 代码。当然,C++ 析构函数使事情复杂化。longjmp
throw
setjmp
catch
评论