解引用空指针会导致未定义的行为。实际上,这通常意味着我的程序将崩溃。但是为什么操作系统不会崩溃呢?因为如果我的程序解引用空指针并由操作系统运行,根据逻辑传递规则,这意味着操作系统尝试解引用空指针。为什么操作系统不进入“未定义行为”状态呢?
解引用空指针会导致未定义的行为。实际上,这通常意味着我的程序将崩溃。但是为什么操作系统不会崩溃呢?因为如果我的程序解引用空指针并由操作系统运行,根据逻辑传递规则,这意味着操作系统尝试解引用空指针。为什么操作系统不进入“未定义行为”状态呢?
C++标准没有定义行为,无论是保证崩溃还是执行其他操作。这并不妨碍操作系统定义行为-它不是C++程序,因此不必遵守C++程序的“规则”[1]。即便如此,操作系统也不会自己取消引用指针。
在大多数现代平台上,访问取消引用指针的目标将导致内存管理硬件引发异常(通常称为“分段错误”或“保护错误”)。内核捕获该异常,可以确定是哪个进程造成的,然后要么终止该进程,要么向其发送信号。
因此,在此类平台上,取消引用空指针的进程的默认行为将是崩溃;操作系统本身没有任何理由崩溃。
[1] 我所说的是程序应该是良好形式的,避免未定义行为的非正式“规则”,不要与由语言标准指定的C++实现的正式“规则”混淆。
每个主要的操作系统都会保护内存访问。你不能简单地编写一个程序来操作未为其分配的内存(假设指针未初始化,那么它可以是任何地址)。因此,每当一个程序尝试访问不属于它的某个地址空间时,操作系统将发送一个信号来终止该程序(导致最终著名的“分段错误”,这对任何C/C++程序员都很熟悉)。
首先,UB代表“任何事情都可能发生”。然而,在实践中,现代操作系统提供了内存保护 - 当程序试图解引用空指针时,该尝试会在CPU内部触发一个中断,由操作系统捕获和处理,然后操作系统停止该程序,接着继续运行,就好像没有发生任何不良事件。
在 UB 方面,逻辑传递没有规则。你的假设是错误的。
UB 的意思是任何事情都可能发生,所以在编写不良的操作系统时,你的程序可能会导致操作系统崩溃。不要排除这种可能性。
此外,你的程序并不是因为解引用 NULL
指针而崩溃,而是因为操作系统告诉它崩溃了。
操作系统设置了一个故障处理程序,如果内存访问违反了操作系统强制执行的规则(例如访问空地址),则会调用该程序。如果您的程序即将对空指针进行解引用操作,则会调用此故障处理程序,并在访问被禁止的内存区域之前结束程序。因此,您的程序实际上从未对空指针进行解引用操作,而是在尝试时被捕获。
检测禁止内存访问的机制通常使用硬件支持,如页表或内存分段。
如果操作系统内核本身对空指针进行解引用操作,则通常在尝试这样做时停止运行。您将看到蓝屏、内核 oops 或类似的情况。如果继续运行,可能会导致“未定义行为”。
请注意,“未定义行为”一词仅在 C 或类似语言中有确切的定义,处理器并不真正关心 - 通常,在没有足够权限访问内存区域时发生的情况在架构上下文中非常明确定义。
因为大多数程序运行在用户模式下,而操作系统运行在内核模式下。内核模式接近物理硬件(他们说“接近金属”)。内核模式程序(操作系统、一些服务、驱动程序等)在CPU的第0环中运行。用户模式程序在更高的环上运行。在CPU的第N环上运行的用户模式程序无法访问任何低于N的程序或内存。如果尝试这样做,将不被允许!
所有程序都获得它们的逻辑地址,并由操作系统分配。当程序尝试读取或写入某些内存时,操作系统会进行逻辑到物理寻址。如果程序尝试访问没有权限的地址,则操作系统将抛出异常。此异常可以由程序本身(同一线程中的本地异常处理程序)处理。如果没有,就会附加全局异常处理程序。如果本地EH无法处理,调试器也可能会介入。这取决于操作系统如何/何时将异常路由到调试器和/或全局异常处理程序。这还取决于异常类型(例如空指针访问),如果操作系统允许本地/全局/调试器处理它或不允许。如果没有人处理它,操作系统将终止进程(并可能创建崩溃转储、分段故障核心转储)。
如果进程没有被调试(只适用于Windows)并且安装了某些调试器,则操作系统可能允许用户调试它。
如果内核模式程序有什么不好的行为,它将会使操作系统崩溃。我不是Linux专家,所以不知道Linux的行为。但是在Windows的情况下,蓝屏死机会让你的显示器发出蓝色的亮光!
抱歉,什么是“逻辑传递性”的规则?操作系统的设计之一就是保护程序免受其他程序的不当行为影响。特别是,操作系统不应因为你的程序尝试做一些愚蠢的事情而崩溃。
在没有内存保护的操作系统中,通过空指针(或任何无效指针)访问确实可能导致操作系统崩溃(如果操作系统恰好将位置0用于某个重要的内容)。
但这与逻辑传递性无关。这与你的程序访问属于另一个程序的内存有关。在这种情况下,任何一个程序都可能崩溃。