我正在开发一个 C++ 应用程序,该应用程序使用另一个团队用 C 编写的库。该库的编写者喜欢在发生错误时调用
exit()
,这会立即结束程序,而无需调用 C++ 应用程序中堆栈上对象的析构函数。应用程序设置了一些系统资源,这些资源在进程结束后不会自动被操作系统回收(共享内存区域、进程间互斥体等),因此这是一个问题。
我拥有应用程序和库的完整源代码,但该库非常完善并且没有单元测试,因此更改它将是一件大事。有没有办法“挂钩”对
exit()
的调用,以便我可以为我的应用程序实现正常关闭?
我正在考虑的一种可能性是创建一个大类,它是应用程序 - 意味着所有清理都将在其析构函数中或其成员之一的析构函数中进行 - 然后在
中的堆上分配这些大对象之一main()
,设置一个全局指针指向它,并使用 atexit()
注册一个处理程序,该处理程序只需通过全局指针删除该对象。这可能有效吗?
有没有已知的好方法来解决这个问题?
在最坏的情况下,您始终可以编写自己的
exit
实现并链接它,而不是系统自己的实现。您可以在那里处理错误,也可以选择自己调用 _exit(2)
。
既然你有了库源代码,那就更简单了 - 只需在构建它时添加一个
-Dexit=myExit
标志,然后提供 myExit
的实现。
使用 atexit 安装退出处理程序并实现所需的行为
如果您想让 C 库在 C++ 中更可用,您也许可以在单独的进程中运行它。然后确保(使用退出处理程序或其他方式)当它退出时,您的主应用程序进程会注意到并抛出异常以展开其自己的堆栈。也许在某些情况下它可以以非致命的方式处理错误。
当然,将库的使用转移到另一个流程可能并不容易或特别有效。您需要做一些工作来包装接口,并通过您选择的 IPC 机制复制输入和输出。
不过,作为从主进程中使用该库的一种解决方法,我认为您描述的应该可行。风险在于您无法识别和隔离需要清理的所有内容,或者将来有人在假设堆栈将正常展开的情况下修改您的应用程序(或您使用的其他组件)。
您可以修改库源以调用运行时或编译时可配置的函数,而不是调用
exit()
。然后编译带有异常处理的库,并用 C++ 实现抛出异常的函数。这样做的问题是,库本身可能会在错误时泄漏资源,因此您必须仅使用该异常来展开堆栈(并且可能会执行一些错误报告)。即使错误对于您的应用而言可能不是致命的,也不要捕获它并继续。
exit
而不是
assert
或abort
,有几点可以再次获得控制:调用
exit
static
声明的对象)的析构函数仍然会执行。这意味着您可以设置一个(几个)全局“资源管理器”对象并在其析构函数中释放资源。正如您已经发现的,您可以使用 atexit
exit
的调用替换为您自己的函数,例如,可以抛出异常。
atexit
处理程序,您可以注册一个“抛出”C++ 异常的处理程序,然后您自己捕获它。例如:
void atexit_handler() {
throw std::runtime_error("exit from user library");
}
void call_library_func() {
std::atexit(atexit_handler);
try {
library_function_you_cannot_change();
} catch (std::runtime_error e) {}
// now you can do other things...
}
一旦您仍然想打电话
exit()
,这可能会很烦人。但是,由于您无法取消注册处理程序,因此控制它的唯一方法是使用全局变量。
static bool _block_exit = false;
void atexit_handler() {
if (_block_exit)
throw std::runtime_error("exit from user library");
}