Perché il compilatore C # traduce questo! = Confronto come se fosse un> confronto?


147

Per puro caso ho scoperto che il compilatore C # trasforma questo metodo:

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

... in questo CIL :

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

... o, se preferisci guardare il codice C # decompilato:

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

Come mai il !=viene tradotto come " >"?

Risposte:


201

Risposta breve:

Non esiste alcuna istruzione "confronta-non-uguale" in IL, quindi l' !=operatore C # non ha una corrispondenza esatta e non può essere tradotto letteralmente.

Esiste tuttavia un'istruzione "confronta-uguale" ( ceq, una corrispondenza diretta con l' ==operatore), quindi nel caso generale x != yviene tradotta come il suo equivalente leggermente più lungo (x == y) == false.

Esiste anche un'istruzione "compare-maggiore di" in IL ( cgt) che consente al compilatore di prendere alcune scorciatoie (ovvero generare un codice IL più breve), uno dei quali è il confronto delle disuguaglianze tra oggetti e null obj != null, che viene tradotto come se fosse " obj > null".

Andiamo in qualche dettaglio in più.

Se non esiste alcuna istruzione "confronta-non-uguale" in IL, come verrà tradotto dal compilatore il seguente metodo?

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

Come già detto sopra, il compilatore trasformerà 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
}

Si scopre che il compilatore non produce sempre questo modello piuttosto prolisso. Vediamo cosa succede quando sostituiamo ycon la costante 0:

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

L'IL prodotto è leggermente più breve rispetto al caso generale:

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

Il compilatore può trarre vantaggio dal fatto che gli interi con segno sono memorizzati nel complemento a due (dove, se i pattern di bit risultanti sono interpretati come numeri interi senza segno - ecco cosa .unsignifica - 0 ha il valore più piccolo possibile), quindi si traduce x == 0come se fosse unchecked((uint)x) > 0.

Si scopre che il compilatore può fare lo stesso per i controlli di disuguaglianza rispetto a null:

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

Il compilatore produce quasi lo stesso IL di IsNotZero:

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

Apparentemente, al compilatore è consentito assumere che il modello di bit del nullriferimento sia il modello di bit più piccolo possibile per qualsiasi riferimento di oggetto.

Questa scorciatoia è esplicitamente menzionata nello standard annotato per l'infrastruttura linguistica comune (prima edizione da ottobre 2003) (a pagina 491, come nota a piè di pagina della Tabella 6-4, "Confronti binari o operazioni di succursale"):

" cgt.unè consentito e verificabile su ObjectRefs (O). Questo è comunemente usato quando si confronta un ObjectRef con null (non esiste un'istruzione" confronta-non-uguale ", che sarebbe altrimenti una soluzione più ovvia)."


3
Risposta eccellente, solo un pò: qui il complemento a due non è rilevante. È importante solo che gli interi con segno siano memorizzati in modo tale che i valori non negativi intnell'intervallo abbiano la stessa rappresentazione in intcui sono presenti uint. È un requisito molto più debole del complemento a due.

3
I tipi senza segno non hanno mai numeri negativi, quindi un'operazione di confronto che si confronta con zero non può trattare nessun numero diverso da zero come inferiore a zero. Tutte le rappresentazioni corrispondenti ai valori non negativi di intsono già state prese con lo stesso valore in uint, quindi tutte le rappresentazioni corrispondenti ai valori negativi di intdevono corrispondere a un valore uintmaggiore di 0x7FFFFFFF, ma non importa quale valore è. (In realtà, tutto ciò che è veramente richiesto è che zero sia rappresentato allo stesso modo in entrambi inte uint.)

3
@hvd: grazie per avermi spiegato. Hai ragione, non è il complemento a due che conta; è il requisito che hai citato e il fatto che cgt.unconsidera un intcome un uintsenza cambiare il modello di bit sottostante. (Immaginate che cgt.unsarebbe prima cercare di underflow fix mappando tutti i numeri negativi a 0. In questo caso, ovviamente, non poteva sostituire > 0per != 0.)
stakx - non contribuendo

2
Trovo sorprendente che confrontare un riferimento a un oggetto con un altro >sia verificabile IL. In questo modo si potrebbero confrontare due oggetti non nulli e ottenere un risultato booleano (che non è deterministico). Questo non è un problema di sicurezza della memoria, ma sembra un design impuro che non è nello spirito generale del codice gestito sicuro. Questo design perde il fatto che i riferimenti agli oggetti sono implementati come puntatori. Sembra un difetto di progettazione della CLI .NET.
usr

3
@usr: Assolutamente! La sezione III.1.1.4 della norma CLI afferma che "I riferimenti agli oggetti (tipo O) sono completamente opachi" e che "le uniche operazioni di confronto consentite sono l'uguaglianza e la disuguaglianza ...". Forse perché i riferimenti oggetto sono non definiti in termini di indirizzi di memoria, lo standard si occupa anche di mantenere concettualmente il riferimento nullo tranne 0 (si veda ad esempio le definizioni di ldnull, initobje newobj). Quindi l'uso di cgt.unconfrontare i riferimenti a oggetti con il riferimento null sembra contraddire la sezione III.1.1.4 in più di un modo.
stakx - non contribuisce più al
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.