Warum übersetzt der C # -Compiler diesen! = Vergleich so, als wäre es ein> Vergleich?

147

Ich habe zufällig entdeckt, dass der C # -Compiler diese Methode umdreht:

static bool IsNotNull(object obj)
{
    return obj != null;
}

… In diese CIL :

.method private hidebysig static bool IsNotNull(object obj) cil managed
{
    ldarg.0   // obj
    ldnull
    cgt.un
    ret
}

… Oder wenn Sie lieber dekompilierten C # -Code betrachten:

static bool IsNotNull(object obj)
{
    return obj > null;   // (note: this is not a valid C# expression)
}

Wie kommt es, dass das !=als " >" übersetzt wird?

stakx - nicht mehr beitragen
quelle

Antworten:

201

Kurze Antwort:

In IL gibt es keine Anweisung "Vergleichen ungleich", daher hat der C # !=-Operator keine genaue Entsprechung und kann nicht wörtlich übersetzt werden.

Es gibt jedoch eine Anweisung "Vergleich gleich" ( ceqeine direkte Korrespondenz mit dem ==Operator), die im allgemeinen Fall x != ywie ihre etwas längere Entsprechung übersetzt wird (x == y) == false.

Es gibt auch eine Anweisung "compare-größer-als" in IL ( cgt), die es dem Compiler ermöglicht, bestimmte Verknüpfungen zu verwenden (dh kürzeren IL-Code zu generieren). Eine davon ist, dass Ungleichheitsvergleiche von Objekten gegen Null so obj != nullübersetzt werden, als ob sie " obj > null".

Lassen Sie uns näher darauf eingehen.

Wenn es in IL keine Anweisung "compare-not-same" gibt, wie wird dann die folgende Methode vom Compiler übersetzt?

static bool IsNotEqual(int x, int y)
{
    return x != y;
}

Wie bereits oben erwähnt, wird sich der Compiler die x != yin(x == y) == false :

.method private hidebysig static bool IsNotEqual(int32 x, int32 y) cil managed 
{
    ldarg.0   // x
    ldarg.1   // y
    ceq
    ldc.i4.0  // false
    ceq       // (note: two comparisons in total)
    ret
}

Es stellt sich heraus, dass der Compiler dieses ziemlich langwierige Muster nicht immer erzeugt. Mal sehen, was passiert, wenn wir ersetzeny die Konstante 0 :

static bool IsNotZero(int x)
{
    return x != 0;
}

Die produzierte IL ist etwas kürzer als im allgemeinen Fall:

.method private hidebysig static bool IsNotZero(int32 x) cil managed 
{
    ldarg.0    // x
    ldc.i4.0   // 0
    cgt.un     // (note: just one comparison)
    ret
}

Der Compiler kann die Tatsache ausnutzen, dass vorzeichenbehaftete Ganzzahlen im Zweierkomplement gespeichert sind (wobei, wenn die resultierenden Bitmuster als vorzeichenlose Ganzzahlen interpretiert werden - das ist, was die.un Mittel - 0 den kleinstmöglichen Wert hat), so dass es so übersetzt, x == 0als ob es wäre unchecked((uint)x) > 0.

Es stellt sich heraus, dass der Compiler genau das Gleiche für Ungleichheitsprüfungen tun kann null :

static bool IsNotNull(object obj)
{
    return obj != null;
}

Der Compiler erzeugt fast die gleiche IL wie für IsNotZero:

.method private hidebysig static bool IsNotNull(object obj) cil managed 
{
    ldarg.0
    ldnull   // (note: this is the only difference)
    cgt.un
    ret
}

Anscheinend darf der Compiler davon ausgehen, dass das Bitmuster des null Referenz das kleinstmögliche Bitmuster für jede Objektreferenz ist.

Diese Verknüpfung wird im Common Language Infrastructure Annotated Standard (1. Ausgabe ab Oktober 2003) ausdrücklich erwähnt (auf Seite 491 als Fußnote zu Tabelle 6-4, "Binäre Vergleiche oder Zweigoperationen"):

" cgt.unist für ObjectRefs (O) zulässig und überprüfbar. Dies wird häufig verwendet, wenn ein ObjectRef mit null verglichen wird (es gibt keine Anweisung" compare-not-same ", die ansonsten eine naheliegendere Lösung wäre)."

stakx - nicht mehr beitragen
quelle
3
Hervorragende Antwort, nur ein Fehler: Das Zweierkomplement ist hier nicht relevant. Es ist nur wichtig, dass vorzeichenbehaftete Ganzzahlen so gespeichert werden, dass nicht negative Werte im intBereich dieselbe Darstellung haben intwie in uint. Das ist eine weitaus schwächere Anforderung als das Zweierkomplement.
3
Vorzeichenlose Typen haben niemals negative Zahlen, daher kann eine Vergleichsoperation, die mit Null verglichen wird, keine Nicht-Null-Zahl als kleiner als Null behandeln. Alle Darstellungen, die den nicht negativen Werten von entsprechen, intwurden bereits mit demselben Wert in aufgenommen uint, daher müssen alle Darstellungen, die den negativen Werten von intentsprechen, einem Wert von uintgrößer als entsprechen 0x7FFFFFFF, aber es spielt keine Rolle, welcher Wert das ist ist. (Eigentlich ist alles, was wirklich benötigt wird, dass Null in beiden intund gleich dargestellt wird uint.)
3
@hvd: Danke für die Erklärung. Sie haben Recht, es kommt nicht auf die Ergänzung von zwei an; Es ist die Anforderung , die Sie erwähnt haben, und die Tatsache, dass cgt.unein intals behandelt wird, uintohne das zugrunde liegende Bitmuster zu ändern. (Stellen Sie sich vor , dass cgt.unzunächst durch Abbildung aller negativen Zahlen auf 0 In diesem Fall fix Unterschreitungen versuchen würde man natürlich nicht ersetzen konnte > 0für != 0.)
stakx - nicht mehr einen Beitrag
2
Ich finde es überraschend, dass der Vergleich einer Objektreferenz mit einer anderen unter Verwendung von >IL überprüfbar ist. Auf diese Weise könnte man zwei Nicht-Null-Objekte vergleichen und ein boolesches Ergebnis erhalten (das nicht deterministisch ist). Das ist kein Problem mit der Speichersicherheit, aber es fühlt sich wie ein unreines Design an, das nicht dem allgemeinen Geist von sicher verwaltetem Code entspricht. Dieses Design verliert die Tatsache, dass Objektreferenzen als Zeiger implementiert sind. Scheint ein Designfehler der .NET CLI zu sein.
usr
3
@usr: Auf jeden Fall! In Abschnitt III.1.1.4 des CLI-Standards heißt es: " Objektreferenzen (Typ O) sind vollständig undurchsichtig" und "die einzigen zulässigen Vergleichsoperationen sind Gleichheit und Ungleichheit ...". Vielleicht , weil Objektreferenzen sind nicht in Bezug auf den Speicheradressen definiert, nimmt der Standard auch Pflege konzeptionell auseinander , um die Null - Referenz zu halten von 0 (siehe zB die Definitionen ldnull, initobjund newobj). Die Verwendung von cgt.unzum Vergleichen von Objektreferenzen mit der Nullreferenz scheint also Abschnitt III.1.1.4 in mehr als einer Hinsicht zu widersprechen.
stakx - nicht mehr