

为什么 C# 访问 null 字段会抛异常?
source link: https://www.cnblogs.com/huangxincheng/p/16395467.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 不得不生成代码来判断。
哈哈,本篇是不是很有意思,希望对大家有帮助。

Recommend
-
42
最近负责的一个 Java 服务经常报 CPU 占用高的告警。于是乘着一次 CPU 高的时候,去用 JFR profile 了一下。(使用 JFR 对 Java 应用 profile 可以参考这篇文章:
-
7
mysql sum函数中对两字段做运算时有null时的情况发布于 今天 01:21 在针对一些数据进行统计汇总的时候,有时会对表中的某些字段进行逻辑运算,如加减乘除,如果要求...
-
10
关于多线程访问http异常处理问题? ...
-
8
AMPlibraryagent异常访问Onedrive下载 先放方法:用任务监视器定位到AMPlibraryagent的路径,然后使用OneDrive的“应用管理”,停止响应AMPlibraryagent的请求。 今天在正常使用MacBook时,偶然发现OneDrive经常会异常同步,反复释放空间无果...
-
9
FastJson 反序列化部分字段为 Null 2020.7.20...
-
5
为什么会有许多表的字段设置为null? 开发中常用的建表工具创建表时字段默认可以为null。 开发人员不能正确区分null和not null的区别,以为默认null可以节省空间。 默认为null,在插入数据...
-
2
华为云:华南-广州区域公网访问异常,目前该故障已恢复 界面 2022年06月15日 08:47 6月13日上午,华为云官微发布客户通知:6月13日1...
-
2
V2EX › Java [mysql 字段] not null 还是 null default
-
6
一台 macOS 电脑,在移动宽带家用宽带内网中,路由器下发的 IP: 192.168.1.207 一台 Ubuntu 云主机,是腾讯云轻量服务器,内网 IP: 10.0.24.5 ,公网 IP: 20.111.10.10
-
7
V2EX › Go 编程语言 发现一个 golang 结构体字段被异常修改的问题,大家帮我看看
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK