C++运行时在非C++程序中调用terminate()是否“合法”?

7
在某些情况下 - 特别是在堆栈展开期间异常逃逸时 - C++运行时会调用terminate(),它必须在死后做一些合理的事情,然后退出程序。当出现“为什么这么严厉”的问题时,通常的答案是“在这种错误情况下没有更合理的做法”。如果整个程序都在C++中,这听起来很合理。
那么如果C++代码在库中,使用该库的程序不是C++呢?这种情况经常发生 - 例如,我可能有一个本地的C++ COM组件,被.NET程序使用。一旦组件代码内部调用terminate(),.NET程序突然异常终止。程序作者首先会想“我不关心C++,为什么这个库让我的程序退出?”
在开发C++库时,如何处理后一种情况?terminate()意外地结束程序是否合理?是否有更好的方法来处理这种情况?

14
不要调用terminate()或任何被定义为调用terminate()的操作。对于exit()abort()、触发SIGKILL或未定义行为,不适用相同的问题吗?如果您的文档说明函数会返回,但它没有返回,则存在错误。程序作者首先应该想“为什么这个库会导致我的程序退出”,然后追查您并要求复仇是正确的。 - Steve Jessop
@Steve:您应该把那个变成一个答案! - Daniel Rikowski
@Steve Jessop: 我不调用 terminate() - C++ 运行时会调用。 - sharptooth
6
因为你们图书馆所做的某件事情,所以请不要再做那件事情。 - anon
4个回答

11
为什么C++运行时会调用terminate()?它不是随机调用,也不是由于编写代码时无法定义和/或避免的情况。它之所以这样做,是因为你的代码执行了一些被定义为会导致调用terminate()的操作,例如在堆栈展开期间从析构函数中抛出异常。
C++标准列出了所有被定义为会导致调用terminate()的情况。如果你不希望调用terminate(),则不要在代码中执行任何这些操作。对于unexpected()abort()等也适用相同的规则。
我认为这与必须避免未定义行为或一般上避免编写错误代码并没有什么不同。你还必须避免被定义但不受欢迎的行为。
也许你有一个特定的例子,在这个例子中很难避免调用terminate(),但是在堆栈展开期间从析构函数中抛出异常不是这种情况。永远不要从析构函数中抛出异常。这意味着设计你的析构函数,使得如果它们执行可能会失败的操作,则析构函数捕获异常并使你的代码继续以一个已定义的状态运行。
有些情况下,由于你的C++代码执行了某些操作(虽然不是调用terminate()),系统会强制结束你的进程,例如如果系统正在过度提交内存而VMM无法履行malloc/new的承诺,则可能会杀死你的进程。但这是一个系统特性,也可能适用于调用你的C++的其他语言。在这种情况下,只要你的调用者知道你的库可能分配内存,那么进程死亡就不是你的代码的错,而是操作系统对低内存条件的定义响应。

6

我认为更基本的问题并不是terminate()函数具体做了什么,而是库的设计。如果该库只设计用于C++,那么异常可以在应用程序中被捕获和处理。

如果该库旨在与非C++应用程序一起使用,它需要提供一个接口来确保没有异常离开该库,例如使用catch(...)语句的接口。


1
假设您在C++中有一个名为cppfunc的函数,并且您正在从另一种语言(例如C或.NET)调用它。我建议您创建一个包装器函数,比如说exportedfunc,如下所示:
int exportedfunc(resultype* outresult, paramtype1 param1, /* ... */)
{
    try {
       *outresult = cppfunc(param1,param2,/* ... */);
       return 0; // 表示成功
    }catch( ... ) {  // 可能需要其他处理程序
       /* 可能设置其他错误状态信息 */
       return -1; // 表示失败
    }
}   
基本上,您需要确保异常不会跨越语言边界...因此,您需要使用一个函数来包装您的C++函数,该函数捕获所有异常并报告状态代码或执行可接受的其他操作,而不是调用std::terminate。

2
这并不能防止terminate()被调用的情况。一旦在堆栈展开期间抛出任何新异常并逃离析构函数,terminate()将被调用,并且之前在调用堆栈上的任何try-catch都无法防止这种情况的发生。 - sharptooth
@sharptooth,你说得对。但是,如果它被调用是因为异常正在进入不支持异常处理的函数,则这将解决该情况。 - Michael Aaron Safyan
@wheaties,如果你想要更多的区分成功和失败,可以使用消息类。然而,在C语言中,使用负值(通常为-1)表示失败,使用0表示成功是标准惯例。 - Michael Aaron Safyan
是的,异常绝对不应该传播到非C++代码中。任何明智的COM开发人员都会注意这一点。 - sharptooth

0

默认的终止处理程序将调用abort。如果您不想要这种行为,请定义自己的终止处理程序并使用set_terminate设置它。


1
你在自己的处理程序中会做什么?标准要求终止程序的执行而不返回给调用者(18.6.3.1)。 - Tadeusz Kopec for Ukraine
是的,这里的关键问题是不允许返回给调用者。 - sharptooth

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