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

时间:2021-10-20 02:32:49

This causes an AccessViolationException to be thrown:

这会导致抛出一个AccessViolationException:

using System;

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

This causes a NullReferenceException to be thrown:

这会导致抛出一个NullReferenceException:

using System;

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

They're both invalid pointers and both violate memory access rules. Why the NullReferenceException?

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

4 个解决方案

#1


43  

This is caused by a Windows design decision made many years ago. The bottom 64 kilobytes of the address space is reserved. An access to any address in that range is reported with a null reference exception instead of the underlying access violation. This was a wise choice, a null pointer can produce reads or writes at addresses that are not actually zero. Reading a field of a C++ class object for example, it has an offset from the start of the object. If the object pointer is null then the code will bomb from reading at an address that's larger than 0.

这是由多年前的Windows设计决定引起的。地址空间的底部64kb被保留。使用null引用异常报告对该范围内任何地址的访问,而不是底层的访问违规。这是一个明智的选择,空指针可以在实际上不是零的地址生成读或写。例如,读取c++类对象的字段,它从对象的开始就有一个偏移量。如果对象指针为空,那么代码将会在读取大于0的地址时爆炸。

C# doesn't have quite the same problem, the language guarantees that a null reference is caught before you can call an instance method of a class. This is however language specific, it is not a CLR feature. You can write managed code in C++/CLI and generate non-zero null pointer dereferences. Calling a method on a nullptr object works. That method will merrily execute. And call other instance methods. Until it tries to access an instance variable or call a virtual method, which requires dereferencing this, kaboom then.

c#没有相同的问题,语言保证在调用类的实例方法之前捕获空引用。这是语言特有的,不是CLR特性。您可以使用c++ /CLI编写托管代码,并生成非零的空指针删除引用。在nullptr对象上调用方法是有效的。这个方法将会愉快地执行。调用其他实例方法。除非它试图访问实例变量或调用虚拟方法(这需要取消引用),否则kaboom将。

The C# guarantee is very nice, it makes diagnosing null reference problems much easier since they are generated at the call site and don't bomb somewhere inside a nested method. And it is fundamentally safer, the instance variable might not trigger an exception on extremely large objects when its offset is larger than 64K. Pretty hard to do in managed code btw, unlike C++. But doesn't come for free, explained in this blog post.

c#保证是非常好的,它使诊断空引用问题变得非常容易,因为它们是在调用站点生成的,并且不会在嵌套方法的某个地方出错。而且它从根本上来说更安全,当实例变量的偏移量大于64K时,它可能不会触发对超大对象的异常。顺便说一句,在托管代码中很难做到,不像c++。但这并不是免费的,在这篇博文中解释道。

#2


17  

A null reference exception and an access violation exception are both raised by the CPU as an access violation. The CLR then has to guess whether the access violation should be specialized to a null reference exception or left as the more general access violation.

一个空引用异常和一个访问违例都被CPU作为访问违例提出。然后,CLR必须猜测访问违例是否应该专门化为null引用异常,还是作为更一般的访问违例而保留。

It is evident from your results that the CLR infers that access violations at addresses very close to 0 are caused by a null reference. Because they were almost certainly generated by a null reference plus field offset. Your use of unsafe code fools this heuristic.

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

#3


3  

This may be a semantics issue.

这可能是一个语义问题。

Your first example is trying to dereference a pointer whose content is the address Int64.MaxValue, not a pointer to a variable that has a value of Int64.MaxValue.

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

Looks like you're trying to read the value stored at the address Int64.MaxValue, which is, apparently not in the range that's owned by your process.

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

Do you mean something like this?

你是说像这样的东西吗?

        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
        }

#4


2  

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

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

Version Information

版本信息

This exception is new in the .NET Framework version 2.0. In earlier versions of the .NET Framework, an access violation in unmanaged code or unsafe managed code is represented by a NullReferenceException in managed code. A NullReferenceException is also thrown when a null reference is dereferenced in verifiable managed code, an occurrence that does not involve data corruption, and there is no way to distinguish between the two situations in versions 1.0 or 1.1.

这个异常在。net Framework 2.0中是新的。在. net框架的早期版本中,非托管代码或不安全托管代码中的访问违例由托管代码中的NullReferenceException表示。如果在可验证的托管代码中取消了null引用,那么也会抛出NullReferenceException,这种情况不涉及数据损坏,而且在1.0或1.1版本中无法区分这两种情况。

Administrators can allow selected applications to revert to the behavior of the .NET Framework version 1.1. Place the following line in the Element section of the configuration file for the application:

管理员可以允许选定的应用程序恢复到. net框架1.1版本的行为。在应用程序的配置文件的元素部分中放置如下一行:

other <legacyNullReferenceExceptionPolicy enabled = "1"/>

其他

#1


43  

This is caused by a Windows design decision made many years ago. The bottom 64 kilobytes of the address space is reserved. An access to any address in that range is reported with a null reference exception instead of the underlying access violation. This was a wise choice, a null pointer can produce reads or writes at addresses that are not actually zero. Reading a field of a C++ class object for example, it has an offset from the start of the object. If the object pointer is null then the code will bomb from reading at an address that's larger than 0.

这是由多年前的Windows设计决定引起的。地址空间的底部64kb被保留。使用null引用异常报告对该范围内任何地址的访问,而不是底层的访问违规。这是一个明智的选择,空指针可以在实际上不是零的地址生成读或写。例如,读取c++类对象的字段,它从对象的开始就有一个偏移量。如果对象指针为空,那么代码将会在读取大于0的地址时爆炸。

C# doesn't have quite the same problem, the language guarantees that a null reference is caught before you can call an instance method of a class. This is however language specific, it is not a CLR feature. You can write managed code in C++/CLI and generate non-zero null pointer dereferences. Calling a method on a nullptr object works. That method will merrily execute. And call other instance methods. Until it tries to access an instance variable or call a virtual method, which requires dereferencing this, kaboom then.

c#没有相同的问题,语言保证在调用类的实例方法之前捕获空引用。这是语言特有的,不是CLR特性。您可以使用c++ /CLI编写托管代码,并生成非零的空指针删除引用。在nullptr对象上调用方法是有效的。这个方法将会愉快地执行。调用其他实例方法。除非它试图访问实例变量或调用虚拟方法(这需要取消引用),否则kaboom将。

The C# guarantee is very nice, it makes diagnosing null reference problems much easier since they are generated at the call site and don't bomb somewhere inside a nested method. And it is fundamentally safer, the instance variable might not trigger an exception on extremely large objects when its offset is larger than 64K. Pretty hard to do in managed code btw, unlike C++. But doesn't come for free, explained in this blog post.

c#保证是非常好的,它使诊断空引用问题变得非常容易,因为它们是在调用站点生成的,并且不会在嵌套方法的某个地方出错。而且它从根本上来说更安全,当实例变量的偏移量大于64K时,它可能不会触发对超大对象的异常。顺便说一句,在托管代码中很难做到,不像c++。但这并不是免费的,在这篇博文中解释道。

#2


17  

A null reference exception and an access violation exception are both raised by the CPU as an access violation. The CLR then has to guess whether the access violation should be specialized to a null reference exception or left as the more general access violation.

一个空引用异常和一个访问违例都被CPU作为访问违例提出。然后,CLR必须猜测访问违例是否应该专门化为null引用异常,还是作为更一般的访问违例而保留。

It is evident from your results that the CLR infers that access violations at addresses very close to 0 are caused by a null reference. Because they were almost certainly generated by a null reference plus field offset. Your use of unsafe code fools this heuristic.

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

#3


3  

This may be a semantics issue.

这可能是一个语义问题。

Your first example is trying to dereference a pointer whose content is the address Int64.MaxValue, not a pointer to a variable that has a value of Int64.MaxValue.

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

Looks like you're trying to read the value stored at the address Int64.MaxValue, which is, apparently not in the range that's owned by your process.

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

Do you mean something like this?

你是说像这样的东西吗?

        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
        }

#4


2  

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

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

Version Information

版本信息

This exception is new in the .NET Framework version 2.0. In earlier versions of the .NET Framework, an access violation in unmanaged code or unsafe managed code is represented by a NullReferenceException in managed code. A NullReferenceException is also thrown when a null reference is dereferenced in verifiable managed code, an occurrence that does not involve data corruption, and there is no way to distinguish between the two situations in versions 1.0 or 1.1.

这个异常在。net Framework 2.0中是新的。在. net框架的早期版本中,非托管代码或不安全托管代码中的访问违例由托管代码中的NullReferenceException表示。如果在可验证的托管代码中取消了null引用,那么也会抛出NullReferenceException,这种情况不涉及数据损坏,而且在1.0或1.1版本中无法区分这两种情况。

Administrators can allow selected applications to revert to the behavior of the .NET Framework version 1.1. Place the following line in the Element section of the configuration file for the application:

管理员可以允许选定的应用程序恢复到. net框架1.1版本的行为。在应用程序的配置文件的元素部分中放置如下一行:

other <legacyNullReferenceExceptionPolicy enabled = "1"/>

其他