为什么分段错误无法恢复?

111

根据我之前的问题,大多数评论都说“不要这样做,你处于一个悬崖状态,必须杀掉一切重新开始”。还有一个“相对安全”的变通方法。

我不理解的是,为什么分段错误本质上是不可恢复的。

在写入受保护的内存被捕获的那一刻,否则就不会发送SIGSEGV信号。

如果写入受保护的内存的时刻可以被捕获,在某个低级别上被还原,并且将SIGSEGV转换为标准的软件异常,我不明白为什么从理论上讲它不能被还原。

请解释一下:为什么在分段错误后程序处于一个未确定的状态,因为非常明显,故障是在内存实际更改之前抛出的(我可能是错的,不知道为什么)。如果是在更改之后抛出,那么人们可以创建一个以一个字节为单位更改受保护内存并不断引发分段错误的程序,最终重编程内核——这是一种存在安全风险的行为,但我们可以看到世界仍然存在。

  1. 分段错误发生的时机是什么(即何时发送SIGSEGV信号)?
  2. 为什么该进程在此后处于未确定状态?
  3. 为什么它无法恢复?
  • 为什么这个解决方案能避免不可恢复的状态呢?它真的能吗?

  • 25
    问题在于大多数情况下,分段错误是因为程序覆盖了不该覆盖的内存,使程序处于某种未知状态。例如:你覆盖了一个缓冲区,在此过程中破坏了像malloc等内存分配函数的内部记账。然后稍后你调用malloc,由于之前提到的破坏导致触发了分段错误。然后呢?这有点像在现实生活中从悬崖上跳下去,一旦跳了就无法恢复了。 - Jabberwocky
    8
    这段话的意思是:“这并没有真正恢复任何东西。以我之前评论的例子为例,它仅仅给你一种恢复的错觉。内部的存储管理仍将被破坏,并且下一次调用 malloc 很可能会再次触发 segmentation fault(分段错误)。”。 - Jabberwocky
    7
    我不同意:在市场上发布的代码的运行版本中,恢复错误不是可行的选项。除了像设备故障这样的特殊故障外,它永远不应该发生,并且只对开发/调试阶段真正感兴趣。如果有任何可能会除以零的情况,程序应该尽早处理不正确的数据,作为算法的一部分。总之,编写预防性代码比实现后期修复要容易得多。 - Weather Vane
    10
    事实是,你的程序根据错误的假设对某些内存进行了操作。一旦发生这种情况,实际程序状态就会偏离预期的程序状态。事实上,在此之前,实际状态已经存在差异。从那时起,关于程序状态的所有假设都不能被信任。在受保护的内存违规时终止程序是防止进一步损害的好方法,虽然不是万无一失,但是是事情出现问题的一个相当好的早期指示器。 - paddy
    11
    假设您有如下代码:std::vector<int> v(1); v[i] = -1;,其中i等于32。这段代码可能不会引发段错误(https://godbolt.org/z/sh8TW34j9),但它可能会破坏堆空间。后续的某些堆函数可能会导致段错误。如果此时您并不知道发生了堆破坏,该如何恢复? - Daniel Langr
    显示剩余19条评论
    16个回答

    98

    什么时候会发生分段错误(=何时发送SIGSEGV信号)?

    当您尝试访问没有访问权限的内存时,例如访问超出数组范围或解引用无效指针时。信号SIGSEGV是标准化的,但不同的操作系统可能以不同的方式实现它。 "分段错误"主要是*nix系统中使用的术语,Windows称之为"访问违规"。

    为什么在此后进程处于未定义行为状态?

    因为程序中的一个或多个变量未按预期进行操作。假设您有一些数组应该存储若干值,但是您没有为所有值分配足够的空间。因此,只有为其分配了空间的部分正确写入,而其余写到数组范围之外的部分则可以保存任何值。操作系统如何知道这些越界值对您的应用程序功能的重要性呢?它对它们的目的一无所知。

    此外,写入允许范围之外的内存通常会破坏其他不相关的变量,这显然是危险的并且可能导致任意的行为。这种错误通常很难追踪。例如,堆栈溢出是这样的分段错误,容易覆盖相邻的变量,除非通过保护机制捕获错误。

    如果我们观察没有任何操作系统和虚拟内存功能、只有原始物理内存的“裸机”微控制器系统的行为,它们将仅按照指令静默地执行 - 例如,覆盖无关变量并继续执行。这反过来可能会导致应用程序发生灾难性行为,特别是在应用程序具有关键任务时。
    “为什么不能恢复?因为操作系统不知道您的程序应该做什么。”
    尽管在上面的“裸机”场景中,系统可能足够聪明,可以将自身置于安全模式并继续执行。关键应用程序(如汽车和医疗技术)不允许停止或重置,因为这本身可能是危险的。它们将更愿意通过限制功能来“蹒跚回家”。
    “为什么这个解决方案避免了那种无法恢复的状态?它真的吗?”
    该解决方案只是忽略错误并继续执行。它不能修复导致错误的问题。这是一个非常肮脏的补丁,setjmp/longjmp函数在一般情况下非常危险,应该避免使用。
    我们必须认识到,分段错误是一个错误的症状,而不是原因

    1
    感谢您详细的回答!这里有一件事情需要补充,关于任务关键型系统(或任何系统):在生产中的大型系统中,人们无法知道段错误发生在哪里,甚至是否发生,因此修复错误而不是症状的建议并不适用。如果系统确实必须停止,有什么缓解措施吗?是否有一种方法可以记录一些可信的信息,在启动新的、干净的进程之前? - Gulzar
    3
    通常会得到一些“核心转储”或类似的东西。但你可以通过编写一个捕获SIGSEGV信号的信号处理程序来实现自己的自定义日志记录。至于防御故障 - 你如何知道未知错误的严重程度? - Lundin
    16
    对于一个生产系统,由于你永远不知道 SIGSEGV 的真实原因,因此你可能不想在这种状态下继续运行应用程序。相反,你希望以这样的方式编写代码,使得在出现这种情况时重新启动应用程序可以最小化数据损失。问题是,你可能认为在你的情况下 SIGSEGV 是不会有问题的,但你可能忽略了某些错误情况,导致应用程序继续运行但生成奇怪或不可预测的结果/行为。 - t.niese
    3
    因为虚拟内存在底层是由MMU硬件设置处理的,应用程序员通常无法访问它。操作系统只是位于应用程序和MMU之间的一层。当您尝试执行数据段外的代码或将代码段视为数据进行访问时,MMU通常会发出硬件异常。而且,为什么您希望它默默地忽略对内存的意外访问?通常来说,诊断越多越好。 - Lundin
    8
    @Yksisarvinen回复:“为什么操作系统应该关心您是否正在覆盖变量?”:它不应该!重点是,既然它不关心,SIGSEGV意味着您正在做某些非常错误的事情,以至于连操作系统都能够发现它是错误的……这可能意味着您的程序状态已经完全损坏。 - ruakh
    显示剩余13条评论

    54
    请解释一下为什么在段错误后程序处于未确定状态?
    我认为这是您根本的误解——SEGV并不会导致未确定状态,它只是其表现症状。问题通常是,在SIGSEGV发生之前,程序已经处于非法且无法恢复的状态,并且从SIGSEGV中恢复也无法改变这一点。
    何时会发生段错误(SIGSEGV)?
    唯一标准的SIGSEGV发生方式是使用调用raise(SIGSEGV)。如果这是SIGSEGV的来源,则可以使用longjump进行恢复。但这是一个实际上永远不会发生的微不足道的情况。有特定于平台的方法可能会导致明确定义的SEGV(例如,在POSIX系统上使用mprotect),并且这些SEGV可能是可恢复的(但可能需要特定于平台的恢复)。然而,未定义行为相关的SEGV的危险通常意味着信号处理程序将非常小心地检查伴随信号的(依赖于平台的)信息,以确保它是所期望的内容。
    为什么此时进程处于未定义行为状态?

    在那个时间点之前,它通常处于未定义的行为状态;只是没有被注意到。这就是C和C++中未定义行为的一个大问题--它没有特定的与之关联的行为,因此可能不会立即被注意到。

    • 为什么这种解决方案避免了那种无法恢复的状态?它真的做到了吗?

    它并没有,只是回到了某个早期的状态,但并没有对导致问题的未定义行为进行任何撤消或识���。


    25
    当程序尝试解引用错误指针时,就会发生段错误(segfault)。此时,程序已经遇到了导致指针出错的错误;尝试解引用它通常不是实际的错误。除非您有意进行一些可能导致段错误的操作,并打算捕获和处理这些情况(请参见下面的部分),否则在错误访问实际故障之前,您将不知道程序中的错误(或宇宙射线翻转位)造成了什么混乱。C和C ++不定义导致分段错误的程序的行为,因此编译器不生成预先考虑恢复尝试的机器代码。即使在手写汇编程序中,除非您期望某些类型的段错误,否则尝试恢复是没有意义的;最多只能在退出之前打印错误消息。如果您在尝试访问的地址处映射了一些新内存,或者将其从只读更改为读+写(在SIGSEGV处理程序中),那么可以让故障指令执行,但这很少能让执行继续。大多数只读内存都是只读的原因,让某些内容写入其中并不会有帮助。而且,通过指针尝试读取某些内容可能需要获取实际位于其他位置的某些特定数据(或根本不读取,因为没有要读取的内容)。因此,将新页面的零映射到该地址将允许执行继续,但不是有用的正确执行。在SIGSEGV处理程序中修改主线程的指令指针,以便在故障指令之后恢复,然后任何加载或存储都将未发生,使用先前在寄存器中的任何垃圾(对于加载),或类似的CISC add reg,[mem] 或其他结果。您链接的捕获SIGSEGV示例取决于编译器以明显的方式生成机器代码,并且setjump / longjump取决于知道哪个代码会导致分段错误,并且在到达未映射的页面之前发生了这种情况,例如printf依赖的 stdout 数据结构,如循环或memcpy可能会发生。

    预期的SIGSEGV,例如JIT沙盒

    像Java或Javascript这样没有未定义行为的语言的JIT需要以明确定义的方式处理空指针解引用,即(Java)在宿主机器上抛出NullPointerException。

    实现Java程序逻辑的机器代码(由JVM的JIT编译器创建的)需要在使用之前至少检查每个引用一次,在任何情况下都无法证明它是非空的(如果它想避免JIT代码故障)。

    但这很昂贵,因此JIT可能通过允许在生成的客户机汇编中发生故障来消除某些空指针检查,尽管这样的故障将首先陷入操作系统,然后才会陷入JVM的SIGSEGV处理程序。

    如果JVM在生成的汇编指令中布置如何,使得任何可能的空指针解引用都会在正确的时间发生,与其他数据的副作用相对应,而且只在应该发生的执行路径上发生(请参见@supercat的答案),那么这是有效的。 JVM将不得不捕获SIGSEGV并longjmp或者从信号处理程序跳转到传递NullPointerException给客户端的代码。

    但关键部分在于JVM假定自己的代码是无错误的,因此可能“损坏”的唯一状态是客户端实际状态,而不是JVM关于客户端的数据。 这意味着JVM能够处理发生在客户端的异常,而不依赖于可能已经损坏的数据。

    但是,如果客户端本身没有预期NullPointerException,因此不知道如何修复情况,那么它可能无法做太多事情。 它可能不应该做的更多,只需打印错误消息并退出或重新启动自己。(几乎与正常的预编译C ++程序相同。)

    当然,JVM需要检查SIGSEGV的故障地址,并找出确切位于哪个客户端代码,以知道在哪里传递NullPointerException。(如果有任何catch块。)如果故障地址根本不在JIT的客户端代码中,则JVM就像任何其他预先编译的C / C ++程序一样segfaulted,不应该做太多事情,只需打印错误消息并退出。(或raise(SIGABRT)触发核心转储。)

    作为JIT JVM,并不能使您更轻松地从自己逻辑中的错误导致的意外段错误中恢复。关键在于有一个沙盒式的客户端程序,您已经确保它无法破坏主程序,并且其故障对于主JVM来说并不是意外的。(您不能允许客户端程序中的“托管”代码具有完全野指针,可能指向任何位置,例如指向客户端程序的代码。但通常情况下这没有问题。但是您仍然可以拥有空指针,使用实际上会在硬件尝试去引用它时出现故障的表示方法。这样做不会让其读写主机状态。)
    有关更多信息,请参见 Why are segfaults called faults (and not aborts) if they are not recoverable? 了解有关segfault的汇编级视图。以及链接到JIT技术的页面错误检查: 另一个技巧是将数组的结尾放在页面的结尾(后面跟着足够大的未映射区域),这样每次访问都可以通过硬件免费进行边界检查。如果您可以静态地证明索引始终为正,并且它不能大于32位,则一切就准备妥当了。

    背景:什么是段错误

    操作系统传递SIGSEGV的常见原因是,当您的进程触发一个页面故障时,操作系统发现该页面“无效”。(也就是说,这是您的问题,而不是操作系统的问题,因此它无法通过将已交换到磁盘上的数据分页回来(硬页故障)或在第一次访问时复制写入或零化新的匿名页面(软页故障),并更新虚拟页面的硬件页表以匹配您的进程逻辑映射。)

    页面错误处理程序无法修复这种情况,因为用户空间线程通常没有请求操作系统将任何内存映射到该虚拟地址。如果它只是尝试恢复用户空间而没有对页表进行任何处理,则相同的指令将再次发生故障,因此内核会传递SIGSEGV。该信号的默认操作是终止进程,但如果用户空间安装了信号处理程序,则可以捕获它。

    其他原因包括(在Linux上)尝试在用户空间运行特权指令(例如x86 #GP“通用保护故障”硬件异常),或在x86 Linux上使用错误对齐的16字节SSE加载或存储(再次是#GP异常)。这可能发生在使用_mm_load_si128而不是loadu手动矢量化代码的情况下,甚至可能是由于具有未定义行为的程序中的自动矢量化导致的:为什么对mmap的内存进行未对齐访问有时会在AMD64上导致段错误? (一些其他操作系统,例如MacOS / Darwin,提供用于错误对齐的SSE的SIGBUS。)


    当程序出现 bug 后,通常才会发生段错误

    这时你的程序状态已经混乱了,例如在你期望得到非空指针的地方却遇到了 NULL 指针或其他无效指针(如某些形式的 use-after free 或被一些无效指针所覆盖)。

    如果幸运的话,它会早早地崩溃并报错,尽可能靠近实际的 bug;如果不幸的话(例如破坏 malloc 的内部书记信息),则要等到有问题的代码执行很长一段时间才会真正崩溃。


    1
    未对齐的访问在大多数 POSIX 系统上会导致 SIGBUS 错误 -- 在 x86 架构的 Linux 中则是一个奇怪的例外。特权指令通常会导致 SIGILL 错误。 - Chris Dodd

    21
    你需要理解的是,分段错误不是问题本身,而是上帝近乎无限的仁慈的例子(根据我大学时期的一位老教授说法)。分段错误是一个迹象,表明某些东西出了大问题,你的程序以为在没有内存的地方访问内存是个好主意。这次访问本身并不是问题所在,问题发生在之前的某个不确定的时间,当时出了问题,最终导致你的程序认为这种访问是一个好主意。在这一点上,访问不存在的内存只是一个症状,但是(这就是上帝的仁慈所在)它是一个容易检测的症状。情况可能更糟糕;它可能会访问有内存可用的内存,只是错误的内存。操作系统不能从这种问题中拯救你。 操作系统无法找出导致程序相信如此荒谬事情的原因,唯一能做的就是关闭一切,在使用起来操作系统无法轻易检测到的方式下避免其他疯狂的行为。通常,大多数操作系统还提供核心转储(程序内存的保存副本),理论上可以用来找出程序认为它在做什么。对于任何复杂的程序来说,这并不是很简单,但这就是操作系统这样做的原因,以防万一。

    12

    虽然您的问题具体涉及段错误,但实际上问题是:

    如果一个软件或硬件组件被命令执行一些无意义甚至不可能的操作,它应该怎么做?什么都不做?猜测实际需要做什么并执行它?还是使用某种机制(例如“抛出异常”)停止发出无意义命令的高层计算?

    许多工程师多年的大量经验表明,最好的答案是停止整个计算,并生成诊断信息,这些信息可以帮助其他人找出问题所在

    除了对受保护或不存在的内存进行非法访问之外,其他“无意义命令”的示例包括告诉CPU将整数除以零或执行不能解码为任何有效指令的垃圾字节。如果使用具有运行时类型检查的编程语言,则尝试调用某些数据类型不支持的操作也是一个示例。

    但是,为什么要让试图除以零的程序崩溃才是更好的选择呢?没有人希望他们的程序崩溃。我们不能将除以零定义为等于某个数字,比如零或73吗?我们不能创建一些可以跳过无效指令而不会故障的CPU吗?也许我们的CPU还可以针对受保护或未映射内存地址的任何读取返回一些特殊值,比如-1,并忽略对受保护地址的写入。再也没有段错误了!耶!

    当然,所有这些都可以做到,但实际上并没有得到什么好处。这里的关键是:虽然没有人希望他们的程序崩溃,但是不崩溃并不意味着成功。人们编写和运行计算机程序是为了做某事,而不只是为了“不崩溃”。如果一个程序足够有缺陷,可以读取或写入随机内存地址或尝试除以零,则即使允许其继续运行,它实现你真正想要的可能性非常低。另一方面,如果在程序尝试疯狂的操作时没有停止它,它可能会做一些你不想要的事情,比如破坏或销毁你的数据。

    历史上,一些编程语言被设计成总是对无意义的命令“做点什么”,而不是引发致命错误。这样做是出于误导的初学者友好的尝试,但结果总是很糟糕的。同样,如果操作系统永远不因段错误而崩溃程序,你的建议也是如此。


    10

    在机器代码层面,许多平台允许那些在特定情况下“期望”分段错误的程序调整内存配置并恢复执行。这可能对实现诸如堆栈监视之类的东西有用。如果需要确定应用程序曾经使用过的最大堆栈量,则可以将堆栈段设置为仅允许访问少量堆栈,然后通过调整堆栈段的边界并恢复代码执行来响应分段错误。

    但是,在C语言层面支持这种语义将极大地阻碍优化。如果写出以下内容:

    void test(float *p, int *q)
    {
      float temp = *p;
      if (*q += 1)
        function2(temp);
    }
    

    编译器可能将对*p的读取和对*q的读取-修改-写入序列视为彼此未排序,并生成仅在*q的初始值不是-1的情况下才读取*p的代码。如果p有效,则这不会影响程序行为,但如果p无效,则这种更改可能导致在增加*q之后发生访问*p的段错误,即使触发错误的访问是在增量之前执行的。

    要使语言能够有效且有意义地支持可恢复的段错误,必须比C标准曾经做过的更详细地记录允许和不允许的优化范围,我认为没有理由期望未来的C标准包括这样的细节。


    C语言中有一个restrict关键字,用于编译器进行优化。 - qwr
    @qwr:restrict关键字允许一些优化,但它无法处理指针保证仅标识相同数组段或不相交数组段的情况,而永远不会标识部分重叠的数组段。此外,由于规范的粗心,限定为restrict的指针与可能基于它们的其他指针之间的相等比较在本质上是有问题的,这两个问题都被clang和gcc“利用”以使它们无用。在像if (restrictPtr == otherPtr) *restrictPtr = 123;这样的结构中,存在歧义... - supercat
    无论在lvalue *restrictPtr 中使用的指针值是否基于 restrictPtr,clang 和 gcc 都不会可靠地识别它(标准中 "restrict 的正式规范" 的方式是这样写的:用 *otherPtr = 123; 替换 *restrictPtr = 123 永远不会可观地影响程序行为,并且由于 *otherPtr = 123; 会使用一个不基于 restrictPtr 的指针访问存储器,将 *restrictPtr = 123; 视为执行相同操作)。 - supercat
    1
    @qwr: 如果对于每个指针p,都有其他指针的三向分裂:那些明确基于p、那些明确不基于p、以及不属于这两种情况的指针,那么在所有角落的情况下,标准就可以更容易推理和正确处理了。如果我们接受一些指针无法归类为明确基于P或明确不基于P,那么我们可以使用简单明了的规则来处理其他所有情况。 - supercat

    9

    这是可以恢复的,但通常不是一个好主意。 例如微软的C++编译器有选项可以将段错误转换为异常。

    您可以查看微软的SEH文档,但即使他们也不建议使用它。


    1
    它只是“可恢复”的意思是该进程不会立即退出。显然,忽略错误并继续前进绝不是一个好主意。 - Luaan

    7

    老实说,如果我可以告诉计算机忽略分段错误的话,我不会选择这个选项。

    通常情况下,分段错误的发生是因为你正在解除引用一个空指针或已释放的指针。当解除引用空指针时,行为是完全未定义的。当引用已释放的指针时,你提取的数据可能是旧值、随机垃圾,或者最糟糕的情况是来自其他程序的值。无论哪种情况,我希望程序能终止并报告无效计算,而不是继续运行。


    6

    分段错误是多年来一直困扰我的问题。我主要在嵌入式平台上工作,因为我们在裸机上运行,所以没有文件系统可用于记录核心转储。系统只是锁定并死亡,可能会通过串行端口发出几个告别字符。那些年中更加启迪人心的时刻之一是当我意识到分段错误(和类似的致命错误)是一件好事情。经历这样的错误并不好,但将它们作为硬性不可避免的失败点是有益的。

    这样的故障并不轻易产生。硬件已经尝试了它所能做的一切,而错误是硬件警告您继续下去是危险的方式。实际上,让整个进程/系统崩溃甚至比继续进行更安全。即使在具有受保护/虚拟内存的系统中,继续执行此类故障也可能使其余系统变得不稳定。

    如果捕获写入受保护内存的时刻

    导致 segfault 的原因不仅仅是写入受保护内存。例如,还可以通过从具有无效值的指针读取来导致 segfault。这可能是由先前的内存损坏(已经造成了损害,因此无法恢复)或缺乏错误检查代码(应该被您的静态分析器和/或测试捕获)引起的。

    为什么它不可恢复?

    你不一定知道问题的原因或范围,所以你无法知道如何从中恢复。如果您的内存已被破坏,您不能信任任何东西。可以从中恢复的情况是您可以在事先检测到问题的情况下使用异常不是解决问题的正确方法。

    请注意,一些此类问题在其他语言(如 C#)中是可以恢复的。这些语言通常具有额外的运行时层,在硬件生成故障之前检查指针地址并抛出异常。但是,像 C 这样的低级语言没有任何这样的功能。

    为什么这个解决方案可以避免不可恢复状态?它真的吗?

    这种技术“有效”,但仅适用于人为制造的简单用例。继续执行和恢复不是相同的事情。相关系统仍处于故障状态,存在未知的内存损坏,您只是选择继续前进而不是听从硬件的建议认真处理问题。此时无法预测程序会执行什么操作。在潜在的内存损坏之后继续执行的程序将是攻击者的早期圣诞礼物。

    即使没有内存损坏,这个解决方案在许多常见用例中也会出现问题。你无法在已经处于受保护的代码块中时进入第二个受保护的代码块(例如内部帮助函数)。在受保护的代码块之外发生的任何段错误都将导致跳转到代码中难以预测的位置。这意味着每行代码都需要在受保护的代码块中,并且你的代码将很难理解。你无法调用外部库代码,因为那些代码不使用此技术,也不会设置setjmp锚点。你的“处理程序”块不能调用库函数或执行涉及指针的任何操作,否则你将面临无休止嵌套的块。某些东西,如自动变量,在longjmp之后可能处于不可预测的状态。

    关于关键任务系统(或任何系统)有一件事情值得注意:在生产中的大型系统中,人们无法知道段错误在哪里,甚至是否存在,因此修复错误而不是症状的建议并不适用。

    我不同意这种想法。我见过的大多数段错误都是由于在使用指针之前未先进行验证而导致的(直接或间接地)。在使用指针之前对它们进行检查将告诉你段错误出现的位置。将复杂语句(例如my_array [ptr1->offsets [ptr2->index]])拆分为多个语句,以便您可以检查中间指针。Coverity等静态分析工具能够很好地找到使用未经验证的指针的代码路径。这不会保护你免受由明显的内存损坏引起的段错误,但无论如何都没有从那种情况中恢复的方法。

    简而言之,我认为我的错误只是访问null等。

    好消息!整个讨论都是毫无意义的。指针和数组索引可以在使用之前进行验证,并且提前检查的代码比等待问题发生并尝试恢复要少得多。

    5
    这可能不是一个完整的答案,它绝不是完整或准确的,但它不适合放在注释中。因此,当您尝试以不应该的方式访问内存(例如在只读状态下对其进行写入或从未映射的地址范围读取)时,可能会发生SIGSEGV。如果您了解足够多的环境信息,这种错误本身可能是可以恢复的。但是,您如何确定为什么首先发生了无效访问呢? 在另一个回答的评论中,您说:“短期实践中,我认为我的错误仅限于访问null而已。”没有应用程序是没有错误的,因此,如果空指针访问可能会发生,为什么您会认为您的应用程序不会出现诸如使用后释放或越界访问“有效”内存位置的情况,这些情况不会立即导致错误或SIGSEGV。 使用已释放的内存或越界访问也可能修改指针以指向无效位置或成为nullptr,但同时还可能更改内存中的其他位置。如果您现在只假设指针未初始化并且您的错误处理仅考虑此问题,则会继续执行与您预期或编译器在生成代码时所期望的不符合状态的应用程序。 在最好的情况下,应用程序将很快在“恢复”后崩溃,在最坏的情况下,某些变量具有错误值,但它将继续使用这些值运行。对于关键应用程序而言,这种疏忽可能比重新启动更具有害性。 但是,如果您知道某个操作在某些情况下可能会导致SIGSEGV,则可以处理该错误,例如,您知道存储器地址有效,但是内存映射到的设备可能不完全可靠,并且可能由于此原因导致SIGSEGV,那么从SIGSEGV中恢复可能是一种有效的方法。

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