探究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字段异常内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#三种判断数据库中取出的字段值是否为空(NULL) 的方法

    1 通过System.DBNull判断,网上大部分都使用这个方法. 复制代码 代码如下: DataTable dt;                               //假设字段为name, dt已经保存了数据dt.rows[0]["name"] == System.DBNull.Value;  //判断第一行数据的name字段是否为空 2 通过IsNull判断 复制代码 代码如下: DataTable dt;                //假设字段为name, dt已

  • 探究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 是一个 n

  • Python 解决中文写入Excel时抛异常的问题

    近期接到业务部门需求,需将统计结果每日发送到业务部门,在调试python脚本的时候,导出的Excel标题为中文,总是抛出以下异常 Traceback (most recent call last): File "totalpx.py", line 99, in <module> export() File "totalpx.py", line 54, in export workbook.save(out_path) File "/usr/li

  • Android跨进程抛异常的原理的实现

    今天接到了个需求,需要用到跨进程抛异常. 怎样将异常从服务端抛到客户端 也就是说在Service端抛出的异常需要可以在Client端接收.印象中binder是可以传异常的,所以aidl直接走起: // aidl文件 interface ITestExceptionAidl { boolean testThrowException(); } // service端实现 public class AidlService extends Service { @Nullable @Override pu

  • 浅析.net core 抛异常对性能影响

    目录 一.前言 二.求证 2.1 使用.net 6 建立了一个简单的web api 项目 新增两个压测接口api接口代码如下 2.2 最终结论 参考文献 异常和正常代码性能旗鼓相当,但是全局过滤器对性能影响比较大,大概降低了60%左右,全局过滤器走了管道,但是这跟微软官方的性能优化又有冲突,想必微软官方也是出于对全局过滤器异常处理的考虑吧.同时对于添加了业务的情况下,这个降低会被稀释,没去做压测对比哈,正常用户体量还不至于被这个给影响到稳定性.所以怎么取舍看自己 一.前言 在.net 相关技术群

  • Android Force Close 出现的异常原因分析及解决方法

    一.原因: forceclose,意为强行关闭,当前应用程序发生了冲突. NullPointExection(空指针),IndexOutOfBoundsException(下标越界),就连Android API使用的顺序错误也可能导致(比如setContentView()之前进行了findViewById()操作)等等一系列未捕获异常 二.如何避免 如何避免弹出Force Close窗口 ,可以实现Thread.UncaughtExceptionHandler接口的uncaughtExcepti

  • 关于django python manage.py startapp 应用名出错异常原因解析

    如题,在控制台运行python manage.py startapp sales 建立一个应用报错异常 1.应用名不能包含下划线等字符 所以app-demo 不能作为应用名被定义 2.manage.py 报错 这时我们要打开manage.py文件 修改前: if name == "main": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangodeom.setting") from dj

  • Java中java.lang.ClassCastException异常原因及解决方法

    通常我们在 OOP 设计中都会使用到继承. ​​但是在继承对象之间的强制转换可能会遇到​​java.lang.ClassCastException​​异常的错误. 错误的日志如下: 19:58:25.010 [http-nio-8080-exec-5] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw

  • Oracle ORA-22908(NULL表值的参考)异常分析与解决方法

    场景如下: --创建类型(type)create or replace type list_obj is table of number;--创建表结构create table test( name varchar2(30) primary key check(regexp_like(name,'^([a-z]|[0-9]|_)+$')), id number not null, version_list list_obj) nested table version_list store as

  • 收集的比较全的automation服务器不能创建对象 异常原因和解决方法第1/2页

    1.automation服务器不能创建对象 只需要运行:regsvr32 scrrun.dll 就可以了 2.未将对象引用设置到对象的实例. 1.ViewState 对象为Unll. 2.DateSet 空. 3.sql语句或Datebase的原因导致DataReader空. 4.声明字符串变量时未赋空值就应用变量. 5.未用new初始化对象. 6.Session对象为空. 7.对控件赋文本值时,值不存在 8.使用Request.QueryString()时,所获取的对象不存在,或在值为空时未赋

  • MySQL中建表时可空(NULL)和非空(NOT NULL)的用法详解

    对于MySQL的一些个规范,某些公司建表规范中有一项要求是所有字段非空,意味着没有值的时候存储一个默认值.其实所有字段非空这么说应该是绝对了,应该说是尽可能非空,某些情况下不可能给出一个默认值. 那么这条要求,是基于哪些考虑因素,存储空间?相关增删查改操作的性能?亦或是其他考虑?该理论到底有没有道理或者可行性,本文就个人的理解,做一个粗浅的分析. 1,基于存储的考虑 这里对存储的分析要清楚MySQL数据行的存储格式,这里直接从这篇文章白嫖一部分结论,文章里分析的非常清楚(其实也是参考<MySQL

随机推荐