为什么*(int*)0 = 0导致访问冲突?

Pet*_*son 26 c# pointers unsafe access-violation

出于教育目的,我正在编写一组方法,这些方法会导致C#中的运行时异常,以了解所有异常是什么以及导致它们的原因.现在,我正在修补导致问题的程序AccessViolationException.

对我来说最明显的方法是写入受保护的内存位置,如下所示:

System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0);
Run Code Online (Sandbox Code Playgroud)

正如我所希望的那样,这引起了人们的注意AccessViolationException.我想更简洁地做到这一点,所以我决定用不安全的代码编写程序,并通过分配0零指针来做(我认为的)完全相同的事情.

unsafe
{
    *(int*)0 = 0;
}
Run Code Online (Sandbox Code Playgroud)

由于无法理解的原因,这引发了一个问题NullReferenceException.我玩了一些,并发现使用*(int*)1而不是抛出一个NullReferenceException,但如果你使用负数,就像*(int*)-1它会抛出一个AccessViolationException.

这里发生了什么?为什么*(int*)0 = 0导致a NullReferenceException,为什么不导致AccessViolationException

Eri*_*ert 29

取消引用空指针时会发生空引用异常; CLR不关心空指针是否是一个带有整数零点的不安全指针,或者是一个托管指针(即对引用类型的对象的引用),其中没有插入零.

CLR如何知道null已被解除引用?CLR如何知道其他一些无效指针何时被解除引用?每个指针指向进程的虚拟内存地址空间中的虚拟内存页面中的某个位置.操作系统跟踪哪些页面有效以及哪些页面无效; 当您触摸无效页面时,它会引发一个由CLR检测到的异常.然后,CLR将其表示为无效访问异常或空引用异常.

如果无效访问是内存的底部64K,则它是空引用异常.否则,它是无效的访问异常.

这解释了为什么解引用零和一个给出空引用异常,以及为什么解除引用-1会给出无效的访问异常; -1是32位机器上的指针0xFFFFFFFF,并且该特定页面(在x86机器上)始终保留供操作系统用于其自身目的.用户代码无法访问它.

现在,您可以合理地问为什么不仅仅为指针零执行空引用异常,而对其他所有内容执行无效访问异常?因为在大多数情况下,当一小部分被解除引用时,这是因为你通过空引用得到了它.想象一下,例如你试图这样做:

int* p = (int*)0;
int x = p[1];
Run Code Online (Sandbox Code Playgroud)

编译器将其转换为道德等同于:

int* p = (int*)0;
int x = *( (int*)((int)p + 1 * sizeof(int)));
Run Code Online (Sandbox Code Playgroud)

这是解除引用4.但从用户的角度来看,p[1]肯定看起来像是null的解引用!这就是报告的错误.