C++函数标记为Extern "C"是否可以抛出异常?

34

我有一些用C++编写的函数,尽管它们只在C++代码中调用,但我希望使用extern "C"来声明它们。是的,我知道这很奇怪,但出于一致性考虑,我们混合了C和C++声明,我想这样做。我只想确保将C++函数声明为extern "C"不会影响抛出行为。

声明应该类似于以下内容:

extern "C" void foo() {throw exception;}

int bar()
{
    try
    {
        foo();
    } catch (exception e) { return 1; }
}

相关问题 - Alexey Frunze
1
答案断言,如果从C++代码中抛出异常到实际的C代码(该代码不知道如何处理异常),则会引发未定义的行为。我认为这是正确的,但您是在询问从C++还是从C调用extern "C"函数?如果您从C++调用它们(而不是从C调用),那么必然会有一个问题:“如果从未从另一种语言调用这些函数,为什么这些函数是extern "C"?”但我认为,仅从C++使用extern "C"调用C++函数意味着没有跨越语言边界,因此没有未定义的行为。 - Jonathan Leffler
感谢所有为回答这个问题做出贡献的人。如果我理解正确,对于我的问题的最终答案是:extern "C" 不会改变异常处理的方式。然而,抛出一个未被捕获并跨越语言边界的异常具有未定义的行为。 - Will Brode
“extern "C"不会改变异常处理方式” - 我并不这样理解答案。特别是/EHsc的情况下,情况是不同的。 - Martin Ba
1
@JonathanLeffler 每个cpp编译器版本都是另一种语言。如果您有一个msvc静态库,它将无法与GCC一起使用,除非它是使用extern "C"的C库。每个编译器都可以随意操纵名称,因此当引用实际存在时,您最终会得到未定义的引用。我认为并没有看到任何理由为什么名称操纵不应该被标准化; 但这是仅针对C ++使用extern "C"的常见原因。例如,我必须创建dll以在gcc中使用来自msvc的代码,因为该代码需要msvc特定的内容。然后,我必须使用extern "C"来抑制名称操纵。 - user13947194
4个回答

18

“extern C”的C++函数是否可以抛出异常?

是的,从语言和编译器的角度来看,它们不会阻止您这样做。

不是的,因为如果您抛出异常,它将被视为未定义行为,因为C++异常越过了语言边界。

实践中,不要这样做。捕获异常并将其转换为错误代码或其他语言可理解的手段。

因此,底线是:不要从标记为 extern "C" 的函数中抛出异常。


5
当代码出现"undefined behavior"时,你不能回答“是的(YES)”。此时你的代码基本上已经崩溃了。需要说明的是,undefined behavior是指未定义行为。 - Martin York
4
@LokiAstari,问题是是否可以抛出,答案是可以。如果从具有C ++链接的函数直接调用extern "C"函数,则没有未定义行为。如果从C中调用同一个函数,则存在未定义行为。只因为链接而产生语言交叉并不一定存在,但这确实是一种异常情况。 - Nathan Ernst
2
@LokiAstari,我不得不反对你的说法:“当情况是未定义行为时,就不能说‘是’。” “未定义行为”不影响它是否可能或应该发生。它只指定结果是未定义的。它并没有指定它永远无法发生。 跨界异常一直在发生。C++->C; C++->Java; C++->Python。区别在于,大多数语言转换库都提供了捕获、转换和重新抛出异常到目标语言的工具。 - Nathan Ernst
2
你的第一个假设是错误的:“如果从具有C++链接的函数直接调用extern“C”函数”,则会产生未定义行为。 C函数的ABI不包含允许堆栈展开所需的信息。 - Martin York
4
我同意@LokiAstari的观点。在我的代码中,我使用C++调用一个抛出异常的extern c函数,但是即使使用catch(...)无法捕获异常。编译器是Intel编译器(icpc)。所有代码都是用C++编写的,没有C语言。链接差异似乎足以破坏堆栈展开。 - Mark Lakata
显示剩余6条评论

8

对于GCC的答案似乎不确定

然而,MSVC文档在这个问题上相对清晰:

  • /EHa/EHs ... 告诉编译器假定声明为extern "C"的函数可能会抛出异常。
  • /EHsc ... 告诉编译器假定声明为extern "C"的函数永远不会抛出C++异常。

因此,对于Visual-C++,它取决于编译器选项是否具有定义行为。


1
+1,但你在其中一个链接评论中错过了gcc的答案:-fexceptions。来自gcc文档:"......在编译需要与用C++编写的异常处理程序正常交互的C代码时,您可能需要启用此选项"。 - EML

2

虽然代码可以编译,但是在被标记为C语言链接的函数中抛出异常会导致未定义行为。由于C语言没有异常处理机制,所以通常你应该仅返回错误码和/或提供一个函数返回最后一次错误的信息。

#include <exception>
extern "C" void foo() {throw std::exception();}

编译良好


1

20
“编译并尝试”对于存在未定义行为的语言来说从来都不是一个好主意。 - Cody Gray
4
然而,这可能会导致您对代码行为做出错误的假设,事实上您根本不应该做出任何假设,因为它是未定义的,编译器可以对其进行任何处理。 - Scott Olson
5
尝试“未定义行为”是毫无意义的,因为测试没有意义,它不会摧毁宇宙,但会让龙从你的鼻子里喷出来。 - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接