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

Mic*_*ray 30 .net c# clr

这导致AccessViolationException抛出:

using System;

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

这会导致NullReferenceException抛出:

using System;

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

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

Han*_*ant 43

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

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

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

  • 是的,编写具有超过4096个公共字段的类的程序员编写了基本上破坏的代码.但是每个人都知道:) (4认同)
  • @JNZ - 对.更糟糕的是,存在相当大的几率,因为内存恰好映射到该地址.当我尝试时发生了这种情况. (3认同)
  • 因此,如果我有一个字段超过 64 KB 的对象,然后在分配 null 时尝试从该对象的实例访问特定字段,则不会抛出“NullReferenceException”,因为字段偏移量将大于65535 并因此计算出超出该地址的地址? (2认同)
  • @Hans:那么我们是否可以得出这样的结论:“someReferenceTypeExpression.SomeField = someValue;”对于一个巨大的对象来说是一个根本不安全的表达式,即使在 C# 中也是如此? (2认同)

Ray*_*hen 17

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

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

  • 在这种情况下,CLR可能会在访问字段之前进行[偏移0处的显式解除引用](http://blogs.msdn.com/b/oldnewthing/archive/2007/08/16/4407029.aspx),避免误报. (5认同)
  • 更准确地说,这种启发式方法来自于Windows永远不会自然地为您提供64KB以下的内存(您可以通过NtAllocateVirtualMemory强制它 - 尽管除了在NTVDM中你甚至不能在Windows8上执行此操作 - 但我离题了).因此,导致AV的任何管理对象都是小于64KB大小的对象的空取消引用.为了避免意外地将对象> 64KB报告为AccessViolation而不是NullDereference,CLR将自动"触摸"任何> 64KB对象的第一个字节,然后从一开始引用超过64KB的字段 (3认同)