Dot-Net

呼叫和 Callvirt

  • October 11, 2008

CIL 指令“Call”和“Callvirt”有什麼區別?

call用於呼叫非虛擬、靜態或超類方法,即呼叫的目標不受覆蓋。callvirt用於呼叫虛方法(因此如果this是覆蓋該方法的子類,則改為呼叫子類版本)。

當執行時執行一條call指令時,它正在呼叫一段確切的程式碼(方法)。毫無疑問它存在於何處。 一旦 IL 被 JITted,在呼叫站點生成的機器程式碼就是一個無條件jmp指令。

相比之下,該callvirt指令用於以多態方式呼叫虛方法。必須在執行時為每次呼叫確定方法程式碼的確切位置。生成的 JITted 程式碼涉及通過 vtable 結構進行的一些間接操作。因此呼叫執行速度較慢,但它更靈活,因為它允許多態呼叫。

請注意,編譯器可以發出call虛擬方法的指令。例如:

sealed class SealedObject : object
{
  public override bool Equals(object o)
  {
     // ...
  }
}

考慮呼叫程式碼:

SealedObject a = // ...
object b = // ...

bool equal = a.Equals(b);

雖然System.Object.Equals(object)是一個虛方法,但在這種用法中,方法的重載是不可能Equals存在的。 SealedObject是密封類,不能有子類。

出於這個原因,.NET 的sealed類可以比它們的非密封類具有更好的方法分派性能。

**編輯:**原來我錯了。C# 編譯器無法無條件跳轉到方法的位置,因為對象的引用(this方法內的值)可能為空。相反,它會發出callvirt執行 null 檢查並在需要時拋出。

這實際上解釋了我在使用 Reflector 的 .NET 框架中發現的一些奇怪程式碼:

if (this==null) // ...

編譯器可以發出this指針 (local0) 為空值的可驗證程式碼,只有 csc 不這樣做。

所以我猜call它只用於類靜態方法和結構。

鑑於這些資訊,現在在我看來,這sealed僅對 API 安全有用。我發現另一個問題似乎表明密封你的課程沒有性能優勢。

**編輯2:**這比看起來的要多。例如下面的程式碼發出一條call指令:

new SealedObject().Equals("Rubber ducky");

顯然,在這種情況下,對象實例不可能為空。

有趣的是,在 DEBUG 建構中,會發出以下程式碼callvirt

var o = new SealedObject();
o.Equals("Rubber ducky");

這是因為您可以在第二行設置斷點並修改o. 在發布版本中,我想呼叫將是 acall而不是callvirt.

不幸的是,我的電腦目前無法使用,但一旦它再次啟動,我將對此進行試驗。

引用自:https://stackoverflow.com/questions/193939