为什么 C# 访问 null 字段会抛异常?
一:背景
1. 一个有趣的话题
最近在看 硬件异常 相关知识,发现一个有意思的空引用异常问题,拿出来和大家分享一下,为了方便讲述,先上一段有问题的代码。
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 字段会抛异常?的更多相关文章
- 扩展方法where方法查询不到数据,不会抛异常,也不是返回的null
如题,“扩展方法where方法查询不到数据,不会抛异常,也不是返回的null”,示例代码如下: Product类: public class Product { private string name ...
- CloudStack的VO在调用setRemoved方法抛异常的原因
今天在开发中发现一个问题,本来想对一个VO对象的removed值赋值,然后去update一下这条记录,一个最简单的set方法,但是在调用时直接抛异常了. 1: public void setRemov ...
- poco json 中文字符,抛异常JSON Exception -->iconv 转换 备忘录。
起因 最近linux服务器通信需要用到json. jsoncpp比较出名,但poco 1.5版本以后已经带有json库,所以决定使用poco::json(linux 上已经用到了poco这一套框架). ...
- iOS开发——网络篇——UIWebview基本使用,NSInvocation(封装类),NSMethodSignature(签名),JavaScript,抛异常,消除警告
一.UIWebView简介 1.UIWebView什么是UIWebViewUIWebView是iOS内置的浏览器控件系统自带的Safari浏览器就是通过UIWebView实现的 UIWebView不但 ...
- C++11 不抛异常的new operator
在google cpp style guide里面明确指出:we don't use exceptions C++11的noexcept关键字为这种选择提供了便利. C++11以前,提及malloc和 ...
- C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常,不存在从对象类型System.Windows.Forms.DateTimePicker到已知的托管提供程序本机类型的映射。
一:C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常 其实,这个问题与C#的垃圾回收有关.垃圾回收器管 理所有的托管对象,所有需要托管数据的.NET语言(包括 C#)都受运行库的 垃圾回收器 ...
- java通过抛异常来返回提示信息
结论:如果把通过抛异常的方式得到提示信息,可以使用java.lang.Throwable中的构造函数: public Throwable(String message) { fillInStackTr ...
- java反射机制(访问私有字段和私有方法)
来自:http://tutorials.jenkov.com/java-reflection/private-fields-and-methods.html 尽管我们通常认为通过JAVA的反射机制来访 ...
- 考虑实现一个不抛异常的swap
Effective C++:参考自harttle land 类的swap实现与STL容器是一致的:提供swap成员函数, 并特化std::swap来调用那个成员函数. class Widget { p ...
随机推荐
- 1.c语言非递归乘法表(帧栈理解)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <stdbool.h> 4 5 typedef stru ...
- 领域驱动模型DDD(二)——领域事件的订阅/发布实践
前言 凭良心来说,<微服务架构设计模式>此书什么都好,就是选用的业务过于庞大而导致代码连贯性太差,我作为读者来说对于其中采用的自研框架看起来味同嚼蜡,需要花费的学习成本实在是过于庞大,不仅 ...
- 在 Docker 上快速运行 Apache Airflow 2.2.4
Docker 安装 Apache Airflow 参考资料 Running Airflow in Docker 安装依赖 Docker Engine Docker Composite 快速运行 Apa ...
- 【云原生小课堂】高性能、高可用、可扩展的MySQL集群如何组建?
本期[云原生小课堂]将带你入门PXC--公认的MySQL集群优选方案.
- Array实现
(一)基本类型数组实现 public class Array { private int[] data; private int size; // 构造函数,传入数组的容量capacity构造Arra ...
- BurpSuite下提示embedded browser initialisation failed(嵌入式浏览器初始化失败)的解决方法
BurpSuite可谓是渗透测试过程经常使用的神器之一,但使用中经常会碰到奇奇怪怪的问题,比如有时抓http包,发送到Repeater(中继器,也叫重发器)模块后,在右边Render模块下,却无法看到 ...
- 2021.12.15 P2328 [SCOI2005]超级格雷码(找规律填空)
2021.12.15 P2328 [SCOI2005]超级格雷码(找规律填空) https://www.luogu.com.cn/problem/P2328 题意: 输出n位B进制的格雷码. 分析: ...
- SMTP协议解读以及如何使用SMTP协议发送电子邮件
电子邮件协议中POP3协议用于接收邮件,SMTP协议用于发送邮件.SMTP的全称为Simple Mail Transfer Protocol,也就是简单邮件传输协议,字如其名. 相较于POP3而言 ...
- Asp.Net在线预览Word文档的解决方案与思路
前几天有个老项目找到我,有多老呢?比我工作年限都长,见到这个项目我还得叫一声前辈. 这个项目目前使用非常稳定,十多年了没怎么更新过,现在客户想加一个小功能:在线预览Word文档. 首先想到的是用第三方 ...
- BUUCTF-Web:[GXYCTF2019]Ping Ping Ping
题目 解题过程 1.题目页面提示?ip=,猜测是让我们把这个当做变量上传参数,由此猜想是命令注入 2.用管道符加上linux常用命令ls(windwos可以尝试dir)试试 所谓管道符(linux)的 ...