Call und Callvirt
Was ist der Unterschied zwischen dem CIL-Anweisungen "Call" und "Callvirt"?
InformationsquelleAutor der Frage Eric Smith | 2008-10-11
Du musst angemeldet sein, um einen Kommentar abzugeben.
Was ist der Unterschied zwischen dem CIL-Anweisungen "Call" und "Callvirt"?
InformationsquelleAutor der Frage Eric Smith | 2008-10-11
Du musst angemeldet sein, um einen Kommentar abzugeben.
call
ist für den Aufruf nicht-virtuellen, statisch, oder superclass-Methoden, D. H., das Ziel des Anrufs ist nicht Gegenstand überschreiben.callvirt
ist für den Aufruf von virtuellen Methoden (so dass, wennthis
ist eine Unterklasse überschreibt die Methode der Unterklasse-version aufgerufen wird, statt).InformationsquelleAutor der Antwort Chris Jester-Young
Wenn die Laufzeitumgebung führt eine
call
Anleitung es ist ein Aufruf zu einem genauen Stück code (Methode). Es gibt keine Frage wo es das gibt. Sobald die IL wurde JITted, der daraus resultierende Maschinencode an der call-site ist eine unbedingtejmp
Unterricht.Dagegen, die
callvirt
Anweisung wird verwendet, um den Aufruf von virtuellen Methoden in eine polymorphe Art und Weise. Die genaue Lage der Methode code muss zur Laufzeit bestimmt für jede und jeden Aufruf. Die daraus resultierende JITted code beinhaltet einige Dereferenzierung durch vtable-Strukturen. Daher der Aufruf ist langsamer ausführen, aber es ist flexibler, da es für polymorphe Aufrufe.Beachten Sie, dass der compiler kann emittieren
call
Anweisungen für virtuelle Methoden. Zum Beispiel:Betrachten aufrufende code:
Während
System.Object.Equals(object)
ist eine virtuelle Methode, die in dieser Verwendung gibt es keine Möglichkeit für eine überlastung derEquals
Methode zu existieren.SealedObject
ist eine versiegelte Klasse keine Unterklassen.Aus diesem Grund .NET
sealed
Klassen können bessere Methode versandfertig Leistung als Ihre nicht-versiegelt-Pendants.EDIT: Stellt sich heraus, ich war falsch. Der C# - compiler ist nicht möglich, ein unbedingter Sprung zu der Methode, die Lage, da das Objekt s reference (der Wert der
this
innerhalb der Methode) kann null sein. Stattdessen strahlt escallvirt
die nicht die null-check und löst, falls erforderlich.Diese tatsächlich erklärt, einige bizarre-code, den ich gefunden in der .NET-framework mit Reflektor:
Es ist möglich, einen compiler zu emittieren überprüfbaren code, die einen null-Wert für die
this
Zeiger (local0), nur die csc nicht tun.Also ich denke
call
ist nur für die Klasse statische Methoden und Strukturen.Angesichts dieser Informationen scheint es nun für mich, dass
sealed
ist nur nützlich für die API-Sicherheit. Ich fand eine andere Frage, die zu suggerieren scheint, gibt es keine performance-Vorteile für die Versiegelung Ihrer Klassen.EDIT 2: Es gibt mehr zu diesem als es scheint. Zum Beispiel der folgende code gibt ein
call
Anleitung:Offensichtlich in einem solchen Fall gibt es keine Möglichkeit, dass der Objekt-Instanz könnte null sein.
Interessanterweise, in einem DEBUG-build, den folgenden code ausgibt
callvirt
:Dies ist, weil Sie könnten setzen Sie einen breakpoint auf die zweite Zeile und ändern Sie den Wert des
o
. In release-builds, die ich vorstellen, der Anruf wäre einecall
eher alscallvirt
.Leider ist mein PC derzeit außer Gefecht, aber ich werde mit diesem experiment, sobald es wieder.
InformationsquelleAutor der Antwort Drew Noakes
Leider ist dies nicht der Fall ist. Callvirt tut eine andere Sache, das macht es nützlich. Wenn ein Objekt eine Methode aufgerufen, es callvirt wird prüfen, ob das Objekt existiert, und wenn nicht, wirft eine NullReferenceException. Rufen Sie einfach springen Sie zu der Position im Speicher, selbst wenn die Objekt-Referenz ist nicht da, und versuchen zum ausführen der bytes in diesem Ort.
Was dies bedeutet ist, dass callvirt wird immer dann verwendet der C# - compiler (nicht sicher über VB) für Klassen, und der Anruf wird immer für structs, weil Sie kann nie null sein oder Unterklassen).
Bearbeiten In Reaktion auf Drew Noakes Kommentar: ja, es scheint, Sie können den compiler zu emittieren, einen Anruf, für jede Klasse, aber nur in den folgenden sehr spezifischen Fall:
HINWEIS Die Klasse nicht abgedichtet sein müssen, damit dies funktioniert.
So wie es aussieht wird der compiler emittieren einen Anruf, wenn all diese Dinge wahr sind:
InformationsquelleAutor der Antwort Cameron MacFarland
Laut MSDN:
Rufen:
Die call-Anweisung ruft die Methode angegeben, nach der Methode Deskriptor übergeben mit der Anweisung. Die Methode descriptor ist ein Metadaten-token zeigt, dass die Methode zu nennen...Das Metadaten-token trägt genügend Informationen, um zu bestimmen, ob der Aufruf an eine statische Methode, die eine Instanz-Methode eine virtuelle Methode ist, oder eine Globale Funktion. In allen diesen Fällen ist die Ziel-Adresse ist bestimmt nicht ganz von der Methode descriptor (vergleichen Sie dies mit der Callvirt-Anweisung für den Aufruf von virtuellen Methoden, wo die Zieladresse hängt auch von den laufzeittyp der Instanz-Referenz geschoben, bevor der Callvirt).
CallVirt:
Der callvirt-Anweisung ruft eine spät gebundene Methode auf ein Objekt. Das ist, die Methode wird ausgewählt basierend auf der Laufzeit-Typ von obj anstatt der compile-Zeit-Klasse sichtbar, in der Methode, die Zeiger. Callvirt verwendet werden können, rufen sowohl in virtuellen als auch Instanz-Methoden.
Also grundsätzlich unterschiedliche Routen genommen werden, der zum aufrufen einer Objekt-Instanz-Methode, die überschrieben oder nicht:
Aufruf: variable -> variable Typ object -> Methode
CallVirt: variable -> Objekt-Instanz -> Objekt Typ object -> Methode
InformationsquelleAutor der Antwort smwikipedia
Eine Sache vielleicht Wert hinzufügen, um den vorherigen Antworten ist,
es scheint nur ein Gesicht auf, wie "IL call" tatsächlich führt,
und zwei Gesichter, die wie "IL callvirt" führt.
Nehmen Sie dieses Beispiel-setup.
Erste, der CIL Körper der FInst() und FExt() ist zu 100% identisch, opcode -, um-opcode
(außer, dass man als "Instanz" und die anderen "statischen")
-- allerdings, FInst() wird aufgerufen mit "callvirt" und FExt() mit "call".
Zweite, FInst() und FVirt() werden aufgerufen, mit "callvirt"
- auch wenn man virtuellen, aber der andere nicht --
aber es ist nicht das "gleiche callvirt" , wirklich ausgeführt werden.
Hier ist, was grob passiert nach JITting:
Der einzige Unterschied zwischen "call" und "callvirt[Instanz]" ist, dass "callvirt[Instanz]" absichtlich versucht, Zugriff auf ein byte aus *pObj, bevor es fordert den direkten Zeiger auf die Instanz-Funktion (um eventuell eine Ausnahme werfen "und dann rechts").
So, wenn du genervt bist durch die Anzahl der Zeiten, die Sie haben, zu schreiben "- Prüfung Teil"
Können Sie nicht drücken "if (this==null) return SOME_DEFAULT_E;" down in ClassD.GetE() selbst
(als "IL callvirt[Instanz]" Semantik verbietet Ihnen, das zu tun)
aber du bist frei, um schieben Sie es in .GetE (), wenn Sie sich bewegen .GetE (), um eine Erweiterung Funktion irgendwo (als "IL call" semantische erlaubt es-aber leider, verlieren den Zugriff auf private member, etc.)
Sagte, die Ausführung von "callvirt[Instanz]" hat mehr Gemeinsamkeiten
mit "call" als mit "callvirt[virtual]", da der letztere zum ausführen einer triple Umweg, um zu finden, die Adresse für Ihre Funktion.
(Dereferenzierung zu typedef base, dann zu base-vtab-oder-etwas-Schnittstelle, dann an die tatsächlichen slot)
Hoffe, das hilft,
Boris
InformationsquelleAutor der Antwort Boris
Nur das hinzufügen zu den oben genannten Antworten, ich denke, dass die änderung gemacht worden lange zurück, so dass Callvirt IL Anweisung erhalten, erzeugt die Instanz-Methoden-und Call-IL-Anweisung wird generiert für statische Methoden.
Referenz :
Pluralsight-Kurs "C# - Sprache - Interna- Teil 1 von Bart De Smet (video-Call-Anweisungen und call stacks in der CLR-IL in a Nutshell)
und auch
https://blogs.msdn.microsoft.com/ericgu/2008/07/02/why-does-c-always-use-callvirt/
InformationsquelleAutor der Antwort Abhilash NK