Sum*_*eet 5 .net c# clr memory-management nullreferenceexception
我们确实遇到过这个特殊问题,也是我们编码/开发生活日或其他日子中最常见的例外情况之一.我的问题是不是关于为什么(我知道它提出,当我们试图访问它实际上指向空引用变量的性质),但它是关于如何由CLR所产生的空引用异常.
有时我被迫认为用于标识对null的引用的机制(可能null是内存中的保留空间)然后通过CLR引发Exception.CLR如何识别并引发此特定异常.操作系统在其中扮演任何角色吗?
我想分享一个关于它的最有趣的主张:
null实际上是CLR已知的所有时间保留的内存空间,并且禁止所有类型的访问.因此,当找到该空间的引用时,它默认通过OS生成访问被拒绝的异常类型,这被CLR解释为NULL引用异常.
我没有找到支持上述陈述的任何文章或帖子,因此很难相信.可能由于我缺少挖掘细节或其他原因,我希望Stackoverflow是最合适的平台之一,我会得到最好的响应.
它不一定(可能有显式检查),但它可以捕获访问冲突异常.
.NET对象将变为本机对象:其字段成为以特定方式布局的内存块,其方法被嵌入到本机机器代码方法中,并且创建了v表或其他虚拟方法重载机制.
然后,访问字段意味着找到对象的地址,添加成员的偏移量,以及读取或写入所引用的内存块.
调用虚方法,意味着找到对象的地址,找到方法表(在对象中设置偏移量),找到方法的地址(在表中设置偏移量),并在该地址调用方法,并传递对象的地址(this指针).
调用非虚方法意味着使用传递的对象的地址(this指针)调用该方法.
显然,如果在问题地址中没有实际对象,则案例1和2将以某种方式出错,而案例3将起作用(但可能反过来导致案例1或2).这有两种主要方法可能出错:
它可以访问内存的任意位是不是真的我们的类型的对象,从而导致各种令人兴奋的和真的很难追查错误(.NET代码一般不会导致任何导致这种情况下).
它可以访问受保护的任意位内存,从而导致访问冲突.
您可能知道来自C,C++或ASM编码的第二种情况.如果没有,你可能仍然会看到一个程序崩溃,并且它的奄奄一息的气息谈论某个地址的访问冲突.如果是这样,你可能已经注意到虽然给出的地址几乎可以是任何东西,但它通常是0x00000000或非常低的东西,如0x00000020.这些是由试图取消引用空指针的代码引起的,无论是访问字段还是调用虚方法(实际上是访问字段然后根据你得到的内容调用).
现在,由于第一个64k或内存总是受到保护,因此取消引用空指针将始终导致第二种情况(访问冲突)而不是第一种情况(任意内存被误用并导致奇怪的"fandango on the core"错误).
这与.NET完全相同(或者更确切地说,由它产生的jitted代码),但是如果(A)访问冲突发生在低于0x00010000的地址,并且(B)发现这样的违规发生在被jitted的代码,然后它变成了一个NullReferenceException,否则它变成了一个AccessViolationException.
我们可以使用不取消引用但访问受保护内存的代码来模拟这一点(我们只会阅读,所以如果我们碰巧碰到不受保护的内存,结果就不会太奇怪了!) :
以下代码将引发AccessViolationException:
unsafe
{
int read = *((int*)long.MaxValue - 8);
}
Run Code Online (Sandbox Code Playgroud)
以下代码将引发NullReferenceException:
unsafe
{
int read = *((int*)8);
}
Run Code Online (Sandbox Code Playgroud)
这两个代码都没有实际取消引用任何内容.两者都会导致访问冲突,但CLR假设后者可能是由空引用引起的(公平性,到目前为止最可能的情况)并引发它.
因此,我们可以看到字段访问如何callvirt导致这种情况.
现在值得注意的是,由于决定不允许C#在空引用上调用方法,即使在安全的情况下这样做,callvirt也会被用作C#中大多数情况的IL,唯一的例外是静态方法或它可以在编译时显示为不在空引用上.(编辑:还有一些其他的情况,编译器可以看到一个callvirt可以被a替换call,即使该方法实际上是虚拟的[如果编译器可以告诉哪个重载会被命中],后面的编译器会更多地这样做通常,虽然它仍然callvirt会比你想象的更频繁地使用).
一个有趣的案例是优化意味着callvirt可以内联调用with的方法,但是在编译时不知道保证非null的方法.在这种情况下,现场访问可能哪里哪里"呼叫"(这是不是一个真正的呼叫)发生,恰恰触发位置之前,必须添加NullReferenceException在开始,而不是在中间,方法.这意味着优化不会改变观察到的行为.