为什么.NET中最低地址空间(非空)的内存访问报告为NullReferenceException?

时间:2011-10-29 17:29:02

标签: c# .net clr

这会导致AccessViolationException被抛出:

using System;

namespace TestApplication
{
    internal static class Program
    {
        private static unsafe void Main()
        {
            ulong* addr = (ulong*)Int64.MaxValue;
            ulong val = *addr;
        }
    }
}

这会导致抛出NullReferenceException

using System;

namespace TestApplication
{
    internal static class Program
    {
        private static unsafe void Main()
        {
            ulong* addr = (ulong*)0x000000000000FF;
            ulong val = *addr;
        }
    }
}

它们都是无效指针,都违反了内存访问规则。 为什么NullReferenceException?

4 个答案:

答案 0 :(得分:43)

这是由多年前制定的Windows设计决定引起的。地址空间的底部64千字节是保留的。报告使用空引用异常而不是基础访问冲突来访问该范围内的任何地址。这是明智的选择,空指针可以在实际上不为零的地址处产生读取或写入。例如,读取C ++类对象的字段时,它与对象的开头有一个偏移量。如果对象指针为null,则代码将在大于0的地址处读取。

C#没有完全相同的问题,语言保证在调用类的实例方法之前捕获空引用。然而,这是特定于语言的,它不是CLR功能。您可以使用C ++ / CLI编写托管代码并生成非零空指针解引用。在nullptr对象上调用方法有效。该方法将快乐地执行。并调用其他实例方法。在它尝试访问实例变量或调用虚拟方法之前,需要解除引用 this ,然后再使用kaboom。

C#保证非常好,它使得诊断空引用问题变得更加容易,因为它们是在调用站点生成的,并且不会在嵌套方法中的某处轰炸。并且它基本上更安全,当实例变量的偏移量大于64K时,它可能不会触发极大对象的异常。与C ++不同,在托管代码中很难做到。但这并不是免费的,在blog post解释。

答案 1 :(得分:17)

CPU引发空引用异常和访问冲突异常作为访问冲突。然后,CLR必须猜测访问冲突是否应该专门针对空引用异常,还是作为更一般的访问冲突而保留。

从您的结果中可以明显看出,CLR推断地址非常接近0的访问冲突是由空引用引起的。因为它们几乎肯定是由空引用加上字段偏移生成的。你对不安全代码的使用愚弄了这种启发式方法。

答案 2 :(得分:3)

这可能是一个语义问题。

您的第一个示例是尝试取消引用内容为地址 Int64.MaxValue的指针,而不是指向具有 Int64.MaxValue的变量的指针。

看起来您正在尝试读取存储在地址Int64.MaxValue中的值,该值显然不在您的流程所拥有的范围内。

你的意思是这样吗?

        static unsafe void Main(string[] args)
        {
            ulong val = 1;// some variable space to store an integer
            ulong* addr = &val;
            ulong read = *addr;

            Console.WriteLine("Val at {0} = {1}", (ulong)addr, read);

#if DEBUG 
            Console.WriteLine("Press enter to continue");
            Console.ReadLine();
#endif
        }

答案 3 :(得分:2)

来自http://msdn.microsoft.com/en-us/library/system.accessviolationexception.aspx

  

版本信息

     

此异常是.NET Framework 2.0版中的新增内容。在早些时候   .NET Framework的一个版本,非托管代码中的访问冲突   或者不安全的托管代码由NullReferenceException表示   托管代码。 null时也会抛出NullReferenceException   引用在可验证的托管代码中被解除引用,即出现   这不涉及数据损坏,也没有办法   区分版本1.0或1.1中的两种情况。

     

管理员可以允许选定的应用程序恢复为   .NET Framework 1.1版的行为。放置以下行   在配置文件的Element部分中   应用程序:

     

其他<legacyNullReferenceExceptionPolicy enabled = "1"/>