【问题标题】:Segmentation Fault and RAII分段错误和 RAII
【发布时间】:2014-09-11 07:16:03
【问题描述】:

这更像是一个哲学问题。

C++ 中,我们有漂亮闪亮的习语 - RAII。但我经常认为它是不完整的。这与我的应用程序可以被 SIGSEGV 杀死的事实不符。

我知道,我知道,像你说的那样的程序格式错误。但令人遗憾的是,在 POSIX(特别是 Linux)上,您可以分配超出物理内存限制并在执行过程中遇到 SIGSEGV,使用正确分配的内存。

你可能会说:“应用程序死了,你为什么要关心那些没有被调用的可怜的析构函数?”。不幸的是,当应用程序终止时,有些资源不会自动释放,例如 文件系统 实体。

我现在非常讨厌设计黑客,为了应对这种情况而破坏良好的应用程序设计。所以,我要的是为这类问题提供一个好的、优雅的解决方案。

编辑

看来我错了,在 Linux 上的应用程序被内核分页器杀死了。在这种情况下,问题仍然相同,但应用程序死亡的原因不同。

代码sn-p:

struct UnlinkGuard
{
    UnlinkGuard(const std::string path_to_file)
        : _path_to_file(path_to_file)
    { }

    ~UnlinkGuard() {
        unlink();
    }

    bool unlink() {
        if (_path_to_file.empty())
            return true;

        if (::unlink(_path_to_file.c_str())) {
            /// Probably some logging.
            return false;
        }

        disengage();
        return true;
    }

    void disengage() {
        _path_to_file.clear();
    }

private:
    std::string _path_to_file;
};

void foo()
{
    /// Pick path to temp file.
    std::string path_to_temp_file = "...";

    /// Create file.
    /// ...

    /// Set up unlink guard.
    UnlinkGuard unlink_guard(path_to_temp_file);

    /// Call some potentially unsafe library function that can cause process to be killed either:
    ///  * by a SIGSEGV
    ///  * by out of memory
    /// ...

    /// Work done, file content is appropriate.
    /// Rename tmp file.
    /// ...

    /// Disengage unlink guard.
    unlink_guard.disengage();
}

成功后我使用文件。失败时,我希望此文件丢失。

如果 POSIX 支持 link()-ing 以前未按文件描述符链接的文件,则可以实现此功能,但没有这样的功能:(。

【问题讨论】:

  • “不幸的是,有些资源在应用程序终止时不会自动释放,例如文件系统实体。” - 嗯,文件描述符在应用程序终止时被内核释放。
  • @Slyps 如果你使用不抛出的new,它会在失败时返回一个空指针。但是普通的new 只会抛出std::bad_alloc
  • @Angew 只有当进程超出虚拟地址空间时,您才会得到空指针/异常。在x64 这几乎是不可能的。它不会失败,因为new (malloc()) 基于mmap()。而mmap() 只是让我们说扩展虚拟地址空间。
  • 好的,我按照你的描述做了,我得到的是一个“被杀死”的应用程序,正如我所期望的那样。我在工作中已经这样做了好几次了——运行的东西要么有一个错误,分配的内存比它应该的多,要么根本不适合该硬件上可用的内存,它总是被 OOM-killer 杀死.这与“SIGSEGV”不同。所以我想知道是什么使它成为 SIGSEGV 而不是 OOM 被杀死?它在系统调用中死亡吗?你会尝试用一些魔法代码来抵抗 OOM 杀手吗?
  • 提供一个实际示例(最好是代码示例)将有助于显示进程被终止时未释放的资源。 “文件系统实体”真的很模糊。

标签: c++ exception segmentation-fault posix raii


【解决方案1】:

所以,我的要求是为这类问题提供一个好的、优雅的解决方案。

不存在,无论是对于 C++ 还是对于其他语言。您在这里面临一个基本的物理现实,而不是设计决策:当用户拔掉插头时会发生什么?没有编程解决方案可以防止这种情况(嗯,有restore-on-restart)。

可以做的是捕捉 POSIX 信号,有时你甚至可以处理它们——但它很脆弱,有很多警告,another discussion on Stack Overflow details

大多数资源在段错误后不应该被清除。如果您仍然想这样做,只需将这些资源(或者更确切地说,用于清理它们的处理程序)收集到一个全局数组 trap SIGSEGV 中,遍历处理程序中的清理例程数组(希望相关内存仍然完好无损),并执行清理。

更具体地说,对于临时文件,在系统的一个临时文件夹中创建它们会有所帮助。据了解,它们并不总是被各自的应用程序清理,而是系统或用户会定期执行清理。

【讨论】:

  • 我接受你的回答,而不是你清楚说明的保罗的原因:No programming solution can guard against that。我 99% 都知道这将是答案,但希望最后死去 :(。在某些情况下,不可能有一个临时目录。我依赖 rename()。你不能跨文件系统重命名文件。我依赖于它的原子性。而且当这些悬空文件(可能)分布在许多目录中时,检测这些悬空文件并不总是便宜的。
【解决方案2】:

通常,无论语言或操作系统如何,解决方案都是在您启动程序时进行清理,而不是(仅)在您终止时进行清理。如果您的程序可以创建在关闭时清理的临时文件,请在启动程序时清理临时文件。

当您的应用程序终止时,操作系统会终止大多数其他内容,例如文件句柄、tcp 连接等。

【讨论】:

  • 是的,如果可能的话,这就是我现在所做的。但有时在开始时不可能做到这一点,因为必须尽快开始需要时间。我说的是服务器应用程序。
  • 正确性 > 速度。找出损坏的应用程序出了什么问题并手动清理它们也需要时间。我能想到的加快清理速度的 3 件事是 (a) 优化资源以进行清理,例如不必检查各种临时文件是否存在,您只需删除创建时放置“清理”文件的整个文件夹,或 (b) 具有执行延迟初始化和初始化清理的子系统的嵌套应用程序,或(c) 将一些逻辑拆分为独立的进程,这些进程在主应用程序的段错误中仍然存在。
  • 是的,我已经考虑了这些观点,但并非所有这些都适用于我的情况。 (a) - 我确实有单独的文件夹,但我没有明确删除,因为在大多数情况下,文件夹是空的,并且在每个小请求上尝试删除都会影响性能(我认为)。 (b) - 性能相同。 (c) - 产生新进程的开销太大,也太复杂了。还是谢谢。
猜你喜欢
  • 2011-03-17
  • 2015-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 2020-10-12
  • 2013-02-04
相关资源
最近更新 更多