探究C#访问null字段会抛异常原因

 更新时间:2022年06月30日 11:21:38   作者:HappyGirl快乐女孩  
本文主要介绍了探究C#访问null字段会抛异常原因,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一:举例说明 

namespace ConsoleApp2
{
    internal class Program
    {
        static Person person = null;
 
        static void Main(string[] args)
        {
            var age = person.age;
 
            Console.WriteLine(age);
        }
    }
 
    public class Person
    {
        public int age;
    }
}
 

由于 person 是一个 null 对象,很显然这段代码会抛异常,那为什么会抛异常呢?要想找原因,需要从最底层的汇编研究起。

二:异常原理分析

1. 从汇编上寻找答案

可以使用 Visual Studio 2022 的反汇编窗口,观察 var age = person.age; 处到底生成了什么。

----------------  var age = person.age;   ----------------
 
081D6154  mov         ecx,dword ptr ds:[4C41F4Ch]  
081D615A  mov         ecx,dword ptr [ecx+4]  
081D615D  mov         dword ptr [ebp-3Ch],ecx  

这三句汇编还是很好理解的,4C41F4Ch 存放的是 person 对象, ecx+4 是取 person.age,最后一句就是将 age 放在 ebp-3Ch 栈位置上,接下来我们来看下 null 时的 ecx 到底是多少,截图如下:

从图中可以看到,此时的 ecx=0000000,如果大家了解 windows 的虚拟内存布局,应该知道在虚拟内存的 0~0x0000ffff 范围内是属于 null 禁入区,凡是落在这个区一概属访问违例,画个图就像下面这样。

到这里原理就搞清楚了,因为 [ecx+4] = [4] 是落在这个 null 区所致, 但是。。。。 大家有没有发现一个问题,对,就是这里的 [ecx+4],因为这里有一个 +4 偏移来取 age 字段,那我能不能在 person 中多定义一些字段,然后取最后一个字段从而从 null 区 冲出去。。。哈哈。

2. 真的可以冲出 null 区吗

有了这个想法之后,我决定在 Person 类中定义 10w 个 age 字段,参考代码如下:

namespace ConsoleApp2
{
    internal class Program
    {
        static Person person = null;
 
        static void Main(string[] args)
        {
            var str = @"public class Person
                        {
                            {0}
                        }";
 
            var lines = Enumerable.Range(0, 100000).Select(m => $"public int age{m};");
 
            var fields = string.Join("\n", lines);
 
            var txt = str.Replace("{0}", fields);
 
            File.WriteAllText("Person.cs", txt);
 
            Console.WriteLine("person.cs 生成完毕");
        }
    }
}
 

代码执行后,Person.cs 就会如期生成,接下来读取 person.age99999 看看有没有奇迹发生,参考代码如下:

    internal class Program
    {
        static Person person = null;
 
        static void Main(string[] args)
        {
            var age = person.age99999;
 
            Console.WriteLine(age);
        }
    }
 

我去,万万没想到,把 ClassLoader 给弄崩了。。。。得,那只能改 20000 个 age 试试看吧,参考代码如下:

    internal class Program
    {
        static Person person = null;
 
        static void Main(string[] args)
        {
            var age = person.age19999;
 
            Console.WriteLine(age);
        }
    }
 

接下来我们将断点放在 var age = person.age19999; 上继续看反汇编代码。

------------- var age = person.age19999;  -------------
0804657E  mov         ecx,dword ptr ds:[49F1F4Ch]  
08046584  mov         dword ptr [ebp-40h],ecx  
08046587  mov         ecx,dword ptr [ebp-40h]  
0804658A  cmp         dword ptr [ecx],ecx  
0804658C  mov         ecx,dword ptr [ebp-40h]  
0804658F  mov         ecx,dword ptr [ecx+13880h]  
08046595  mov         dword ptr [ebp-3Ch],ecx  

从上面的汇编代码可以看出几点信息。

  • 汇编代码行数多了。
  • ecx+13880h 冲出了 null 区(FFFF) 的边界。

接下来单步调试汇编,发现在 cmp dword ptr [ecx],ecx 处抛了异常。。。

大家都知道此时的 ecx 的地址是 0 ,从 ecx 上取内容肯定会抛访问违例,而且这段代码很诡异,一般来说 cmp 之后都是类似 jz,jnz 跳转指令,而它仅仅是个半残之句。。。

从这些特征看,这是 JIT 故意在取偏移之前尝试判断 ecx 是不是 null,动机不纯哈。。。。

三:总结

从这些分析中可以得知,JIT 还是很智能的。

  • 当偏移值落在 0~FFFF 禁入区内,JIT 就不生成判断代码来减少代码体积。
  • 在偏移值冲出了 0~FFFF 禁入区,JIT 不得不生成代码来判断。

到此这篇关于探究C#访问null字段会抛异常原因的文章就介绍到这了,更多相关C# null字段异常内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#常用的命名规则汇总

    C#常用的命名规则汇总

    这篇文章主要介绍了C#常用的命名规则,较为详细的汇总了包括类、变量、方法、属性等的命名规则,具有很好的参考借鉴价值,需要的朋友可以参考下
    2014-11-11
  • 遍历Hashtable 的几种方法

    遍历Hashtable 的几种方法

    遍历Hashtable 的几种方法...
    2007-03-03
  • C#遍历操作系统下所有驱动器的方法

    C#遍历操作系统下所有驱动器的方法

    这篇文章主要介绍了C#遍历操作系统下所有驱动器的方法,涉及C#中DriveInfo类GetDrivers方法的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • C# 获取进程退出代码的实现示例

    C# 获取进程退出代码的实现示例

    这篇文章主要介绍了C# 获取进程退出代码的实现示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • 详解如何在C#中接受或拒绝Excel中的修订

    详解如何在C#中接受或拒绝Excel中的修订

    修订功能可以跟踪文档所有的修改,了解修改的过程,这对于团队协同文档编辑、审阅是非常有用的一个功能。本文将详细为您介绍如何接受或拒绝 Excel 中的修订,感兴趣的可以收藏一下
    2022-12-12
  • C#使用SQL Dataset数据集代码实例

    C#使用SQL Dataset数据集代码实例

    今天小编就为大家分享一篇关于的文章,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • C#自定义针对URL地址的处理类实例

    C#自定义针对URL地址的处理类实例

    这篇文章主要介绍了C#自定义针对URL地址的处理类,实例分析了C#解析URL地址实现URl参数加密解密及参数解析的技巧,非常具有实用价值,需要的朋友可以参考下
    2015-03-03
  • C#接口(Interface)用法分析

    C#接口(Interface)用法分析

    这篇文章主要介绍了C#接口(Interface)用法,较为详细的分析了C#中接口的功能、实现及使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-03-03
  • C#使用浏览按钮获得文件路径和文件夹路径的方法

    C#使用浏览按钮获得文件路径和文件夹路径的方法

    这篇文章主要介绍了C#使用浏览按钮获得文件路径和文件夹路径的方法,结合实例形式分析了C#浏览器事件响应及文件操作相关技巧,需要的朋友可以参考下
    2017-05-05
  • 理解C#编程中的静态类和静态成员以及密封类

    理解C#编程中的静态类和静态成员以及密封类

    这篇文章主要介绍了理解C#编程中的静态类和静态成员以及密封类,注意类成员的相关访问限制和类的继承问题,需要的朋友可以参考下
    2016-01-01

最新评论