探究C#訪問null字段會(huì)拋異常原因
一:舉例說明
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 是一個(gè) null 對(duì)象,很顯然這段代碼會(huì)拋異常,那為什么會(huì)拋異常呢?要想找原因,需要從最底層的匯編研究起。
二:異常原理分析
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 對(duì)象, ecx+4 是取 person.age,最后一句就是將 age 放在 ebp-3Ch 棧位置上,接下來我們來看下 null 時(shí)的 ecx 到底是多少,截圖如下:

從圖中可以看到,此時(shí)的 ecx=0000000,如果大家了解 windows 的虛擬內(nèi)存布局,應(yīng)該知道在虛擬內(nèi)存的 0~0x0000ffff 范圍內(nèi)是屬于 null 禁入?yún)^(qū),凡是落在這個(gè)區(qū)一概屬訪問違例,畫個(gè)圖就像下面這樣。

到這里原理就搞清楚了,因?yàn)?[ecx+4] = [4] 是落在這個(gè) null 區(qū)所致, 但是。。。。 大家有沒有發(fā)現(xiàn)一個(gè)問題,對(duì),就是這里的 [ecx+4],因?yàn)檫@里有一個(gè) +4 偏移來取 age 字段,那我能不能在 person 中多定義一些字段,然后取最后一個(gè)字段從而從 null 區(qū) 沖出去。。。哈哈。
2. 真的可以沖出 null 區(qū)嗎
有了這個(gè)想法之后,我決定在 Person 類中定義 10w 個(gè) 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 生成完畢");
}
}
}
代碼執(zhí)行后,Person.cs 就會(huì)如期生成,接下來讀取 person.age99999 看看有沒有奇跡發(fā)生,參考代碼如下:
internal class Program
{
static Person person = null;
static void Main(string[] args)
{
var age = person.age99999;
Console.WriteLine(age);
}
}

我去,萬萬沒想到,把 ClassLoader 給弄崩了。。。。得,那只能改 20000 個(gè) age 試試看吧,參考代碼如下:
internal class Program
{
static Person person = null;
static void Main(string[] args)
{
var age = person.age19999;
Console.WriteLine(age);
}
}
接下來我們將斷點(diǎn)放在 var age = person.age19999; 上繼續(xù)看反匯編代碼。
------------- 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
從上面的匯編代碼可以看出幾點(diǎn)信息。
- 匯編代碼行數(shù)多了。
- ecx+13880h 沖出了 null 區(qū)(FFFF) 的邊界。
接下來單步調(diào)試匯編,發(fā)現(xiàn)在 cmp dword ptr [ecx],ecx 處拋了異常。。。

大家都知道此時(shí)的 ecx 的地址是 0 ,從 ecx 上取內(nèi)容肯定會(huì)拋訪問違例,而且這段代碼很詭異,一般來說 cmp 之后都是類似 jz,jnz 跳轉(zhuǎn)指令,而它僅僅是個(gè)半殘之句。。。
從這些特征看,這是 JIT 故意在取偏移之前嘗試判斷 ecx 是不是 null,動(dòng)機(jī)不純哈。。。。
三:總結(jié)
從這些分析中可以得知,JIT 還是很智能的。
- 當(dāng)偏移值落在
0~FFFF禁入?yún)^(qū)內(nèi),JIT 就不生成判斷代碼來減少代碼體積。 - 在偏移值沖出了
0~FFFF禁入?yún)^(qū),JIT 不得不生成代碼來判斷。
到此這篇關(guān)于探究C#訪問null字段會(huì)拋異常原因的文章就介紹到這了,更多相關(guān)C# null字段異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c#下注冊(cè)表操作的一個(gè)小細(xì)節(jié)
c#下注冊(cè)表操作的一個(gè)小細(xì)節(jié)...2007-11-11
C#實(shí)現(xiàn)Windows Form調(diào)用R進(jìn)行繪圖與顯示的方法
眾所周知R軟件功能非常強(qiáng)大,可以很好的進(jìn)行各類統(tǒng)計(jì),并能輸出圖形。下面介紹一種R語言和C#進(jìn)行通信的方法,并將R繪圖結(jié)果顯示到WinForm UI界面上的方法,文中介紹的很詳細(xì),需要的朋友可以參考下。2017-02-02
C#實(shí)現(xiàn)連接SQL Server2012數(shù)據(jù)庫并執(zhí)行SQL語句的方法
這篇文章主要介紹了C#實(shí)現(xiàn)連接SQL Server2012數(shù)據(jù)庫并執(zhí)行SQL語句的方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了C#連接SQL Server2012數(shù)據(jù)庫并執(zhí)行查詢、插入等操作的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10

