首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Emit 實戰筆記:值類型與引用類型在 IL 中的加載差異

     編輯:關於.NET

在用 System.Reflection.Emit 動態生成構造函數時,遇到一個經典的 AccessViolationException:new Random(DateTime.Now.Millisecond) 看似正常,運行卻崩潰。經對比 Reflector 反編譯的標準 IL,發現缺少兩條關鍵指令——這正是值類型與引用類型在評估棧上的根本差異。

現場還原

// 目標 C# 代碼
private Random _rand = new Random(DateTime.Now.Millisecond);

標準 IL (Reflector 產出)

.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
    .maxstack 3
    .locals init ([0] valuetype [mscorlib]System.DateTime CS$0$0000)
    L_0000: ldarg.0
    L_0001: call       valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
    L_0006: stloc.0                    // 存入局部變量
    L_0007: ldloca.s   CS$0$0000       // 關鍵:取局部變量**地址**
    L_0009: call       instance int32 [mscorlib]System.DateTime::get_Millisecond()
    L_000e: newobj     instance void [mscorlib]System.Random::.ctor(int32)
    L_0013: stfld      class [mscorlib]System.Random EmitExamples.Animal::rand
    L_0018: ldarg.0
    L_0019: call       instance void [mscorlib]System.Object::.ctor()
    L_001e: nop
    L_001f: ret
}

錯誤 IL (手寫版,缺兩行)

L_0001: call       valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
L_0006: call       instance int32 [mscorlib]System.DateTime::get_Millisecond()  // 直接在棧上調用,錯誤!
L_000e: newobj     instance void [mscorlib]System.Random::.ctor(int32)

核心差異解析

特性值類型引用類型
棧上佔用直接包含數據存儲引用指針
調用實例方法需 ldloca 取地址傳 thisldloc 直接傳引用
典型指令對stloc / ldlocastloc / ldloc

DateTime 是結構體。call get_Now() 返回值類型實例壓棧,若直接 call get_Millisecond(),CLR 會嘗試從評估棧頂讀取 this 指針,實際卻是結構體內容,導致內存訪問越界。

正確 Emit 代碼片段

var nowLocal = il.DeclareLocal(typeof(DateTime));
il.Emit(OpCodes.Call, typeof(DateTime).GetProperty("Now").GetGetMethod());
il.Emit(OpCodes.Stloc, nowLocal);        // 存局部變量
il.Emit(OpCodes.Ldloca, nowLocal);       // 取地址
il.Emit(OpCodes.Call, typeof(DateTime).GetProperty("Millisecond").GetGetMethod());
il.Emit(OpCodes.Newobj, typeof(Random).GetConstructor(new[] { typeof(int) }));

避坑檢查表

  1. 所有 struct (含 DateTime, Guid, Decimal, 自定義結構) 調用實例成員前必須 ldloca。
  2. 用 il.DeclareLocal 顯式聲明局部變量,避免匿名佔位符混淆。
  3. 寫完 IL 先用 PEVerify.exe 驗證,再運行測試。

這個細節折騰了整個下午,記錄下來與大家共勉。若有更深層次的 CLR 實現見解,歡迎留言補充。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved