为啥这个不安全的代码会抛出 NullReferenceException?
Posted
技术标签:
【中文标题】为啥这个不安全的代码会抛出 NullReferenceException?【英文标题】:Why does this unsafe code throw a NullReferenceException?为什么这个不安全的代码会抛出 NullReferenceException? 【发布时间】:2012-01-28 16:05:24 【问题描述】:我正在使用不安全代码解决Code Golf, 上的一个问题,但我发现了一些我无法解释的东西。这段代码:
unsafe
int i = *(int*)0;
由于访问冲突(Segfault)而崩溃,但这段代码:
unsafe
*(int*)0=0;
抛出 NullReferenceException。在我看来,第一个执行读取,第二个执行写入。一个异常告诉我,CLR 中的某处正在拦截写入并在操作系统终止进程之前停止写入。为什么在写入时会发生这种情况,而在读取时不会发生这种情况?如果我使指针值足够大,它会在写入时出现段错误。这是否意味着 CLR 知道有一块内存是保留的,甚至不会尝试写入?那么,为什么它允许我尝试从该块中读取?我在这里完全误解了什么吗?
编辑:
有趣的是:System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0);
给了我访问冲突,而不是 NullReference。
【问题讨论】:
在我的机器上都抛出一个NullReferenceException
。
推测:第二个抛出 NullReferenceException 是因为编译器能够确定这是它总是会做的事情,并用 throw 语句替换了异常。
@MikeNakis:我向你保证,编译器并没有那么复杂。
两个 sn-ps 代码都会导致非托管 AccessViolation 异常(异常代码 0xc0000005),然后由 .NET 异常处理代码转换为 NullReferenceException,因为访问发生在虚拟内存地址的较低 64KB空间。很难猜出为什么您在第一次 sn-p 时没有获得 NRE。您将获得带有*(int*)-1
的AVE。更多关于这里的信息:***.com/a/7940659/17034
@EricLippert 哦,好的。我想我现在可以认为自己拥有良好的权威。
【参考方案1】:
当然,第一个例外是有道理的——您正试图从内存地址 0 中读取。第二个例外更有趣一些。 :P 在 C++ 中,有一个名为 NULL 的宏/常量,其值为 0。它用于无效的指针地址 - 很像 C# 中用于引用类型的空值。由于 C# 引用在内部是指针,因此当您尝试从该地址读取/写入时会发生 NullReferenceException - 地址 NULL 或 0;事实上,从 0 到 64K 的地址在所有进程中(在 Windows 中)都是无效的,以便捕捉程序员的错误。确切的异常或错误可能会因计算机硬件或 Windows/.NET Framework 版本而略有不同,但您应该会收到两个代码 sn-ps 的错误。
至于读/写随机地址时的段错误,那是由于操作系统对每个进程的隔离。你不能摆弄其他进程的代码或数据——至少不合法。
【讨论】:
我也这么认为,但是很多值都会给出 NullReferenceException,而不仅仅是零。我得到它的价值高达 100k 左右。之后,一些值执行没有错误(我假设它们在我的程序的内存空间中,但我不确定),其他的则给出 AccessViolationExceptions。 为了在开发周期的早期捕捉程序员的错误,从0到64K的虚拟地址在所有进程中都是无效的。我还假设有效地址在您进程的保留内存中。 有道理*(int*)65536 = 0
是第一个成功运行的值。
@CMP:例如,取消引用 0x00000004 也是一个空引用异常,因为最有可能出现取消引用 4 的情况类似于 int* x = &intArray[1]; int y = *x;
。如果 int 数组为 null,则其 0 元素将为 0,其 1 元素将为 4。取消引用 4 通常是因为您通过 null 到达那里。
"至于读/写随机地址时的段错误,这是由于操作系统对每个进程的隔离。你不能摆弄其他进程的代码或数据——至少不合法。”没有多大意义。您通过正常读/写访问的所有用户模式地址都在您自己的进程中。每个进程都有自己的虚拟内存。要访问其他进程的内存,您需要Read/WriteProcessMemory
。以上是关于为啥这个不安全的代码会抛出 NullReferenceException?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 BufferedStream.Write 会抛出“此流不支持查找操作”?
为啥当客户端断开连接时这个简单的 websocket 代码会抛出?