Mengapa kompiler C # menerjemahkan ini! = Perbandingan seolah-olah itu adalah> perbandingan?

147

Saya kebetulan menemukan bahwa kompiler C # mengubah metode ini:

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

... ke dalam CIL ini :

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

… Atau, jika Anda lebih suka melihat kode C # yang dikompilasi:

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

Kenapa !=bisa diterjemahkan sebagai " >"?

stakx - tidak lagi berkontribusi
sumber

Jawaban:

201

Jawaban singkat:

Tidak ada instruksi "bandingkan-tidak-sama" dalam IL, sehingga !=operator C # tidak memiliki korespondensi yang tepat dan tidak dapat diterjemahkan secara harfiah.

Namun ada instruksi "bandingkan-sama" ( ceq, korespondensi langsung ke ==operator), jadi dalam kasus umum, x != yditerjemahkan seperti padanan yang sedikit lebih panjang (x == y) == false.

Ada juga instruksi "bandingkan-lebih-dari-dalam" di IL ( cgt) yang memungkinkan kompiler mengambil jalan pintas tertentu (yaitu menghasilkan kode IL lebih pendek), salah satunya adalah bahwa perbandingan ketidaksetaraan objek terhadap nol,, obj != nullditerjemahkan seolah-olah mereka " obj > null".

Mari kita bahas lebih detail.

Jika tidak ada instruksi "bandingkan-tidak-sama" dalam IL, lalu bagaimana metode berikut akan diterjemahkan oleh kompiler?

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

Seperti yang sudah dikatakan di atas, kompiler akan mengubahnya x != ymenjadi (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
}

Ternyata kompiler tidak selalu menghasilkan pola yang bertele-tele. Mari kita lihat apa yang terjadi ketika kita mengganti ydengan konstanta 0:

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

IL yang diproduksi agak lebih pendek daripada dalam kasus umum:

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

Kompilator dapat mengambil keuntungan dari fakta bahwa bilangan bulat yang ditandatangani disimpan dalam komplemen dua (di mana, jika pola bit yang dihasilkan ditafsirkan sebagai bilangan bulat yang tidak ditandatangani - itulah .unartinya - 0 memiliki nilai sekecil mungkin), sehingga menerjemahkan x == 0seolah-olah itu adalah unchecked((uint)x) > 0.

Ternyata kompiler dapat melakukan hal yang sama untuk pemeriksaan ketimpangan terhadap null:

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

Kompiler menghasilkan IL yang hampir sama dengan untuk IsNotZero:

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

Rupanya, kompiler diperbolehkan untuk mengasumsikan bahwa pola bit dari nullreferensi adalah pola bit terkecil yang mungkin untuk referensi objek apa pun.

Pintasan ini secara eksplisit disebutkan dalam Common Language Infrastructure Annotated Standard (edisi pertama dari Oktober 2003) (di halaman 491, sebagai catatan kaki pada Tabel 6-4, "Perbandingan Biner atau Operasi Cabang"):

" cgt.undiperbolehkan dan dapat diverifikasi pada ObjectRefs (O). Ini biasanya digunakan ketika membandingkan ObjectRef dengan nol (tidak ada instruksi" bandingkan-tidak-sama ", yang kalau tidak akan menjadi solusi yang lebih jelas)."

stakx - tidak lagi berkontribusi
sumber
3
Jawaban yang sangat bagus, hanya satu nit: komplemen dua tidak relevan di sini. Ini hanya masalah yang menandatangani bilangan bulat disimpan sedemikian rupa sehingga nilai-nilai non-negatif dalam intkisaran memiliki representasi yang sama intseperti ketika mereka masuk uint. Itu persyaratan yang jauh lebih lemah daripada komplemen dua.
3
Jenis yang tidak ditandatangani tidak pernah memiliki angka negatif, jadi operasi perbandingan yang membandingkan dengan nol tidak dapat memperlakukan angka bukan nol sebagai kurang dari nol. Semua representasi yang sesuai dengan nilai non-negatif inttelah diambil dengan nilai yang sama di uint, jadi semua representasi yang sesuai dengan nilai negatif intharus sesuai dengan beberapa nilai uintlebih besar dari 0x7FFFFFFF, tetapi tidak terlalu penting nilai mana yang adalah. (Sebenarnya, semua yang benar-benar diperlukan adalah nol diwakili dengan cara yang sama di kedua intdan uint.)
3
@ DVD: Terima kasih telah menjelaskan. Anda benar, bukan pelengkap dua yang penting; itu adalah persyaratan yang Anda sebutkan dan fakta yang cgt.unmemperlakukan intsebagai uinttanpa mengubah pola bit yang mendasarinya. (Bayangkan bahwa cgt.unpertama akan mencoba untuk underflows memperbaiki dengan memetakan semua angka negatif ke 0. Dalam kasus Anda jelas tidak bisa menggantikan > 0untuk != 0.)
stakx - tidak lagi memberikan kontribusi
2
Saya menemukan itu mengejutkan bahwa membandingkan referensi objek dengan yang lain menggunakan >dapat diverifikasi IL. Dengan begitu orang dapat membandingkan dua objek non-null dan mendapatkan hasil boolean (yang non-deterministik). Itu bukan masalah keamanan memori tetapi rasanya seperti desain kotor yang tidak dalam semangat umum kode yang dikelola dengan aman. Desain ini membocorkan fakta bahwa referensi objek diimplementasikan sebagai pointer. Tampak seperti cacat desain. NET CLI.
usr
3
@ usr: Benar-benar! Bagian III.1.1.4 dari standar CLI mengatakan bahwa "Referensi objek (tipe O) benar-benar buram" dan bahwa "satu-satunya operasi perbandingan yang diizinkan adalah persamaan dan ketidaksamaan ...." Mungkin karena referensi objek yang tidak didefinisikan dalam hal alamat memori, standar juga membutuhkan perawatan untuk konseptual menjaga referensi null selain 0 (lihat misalnya definisi ldnull, initobjdan newobj). Jadi penggunaan cgt.ununtuk membandingkan referensi objek terhadap referensi nol tampaknya bertentangan dengan bagian III.1.1.4 dalam lebih dari satu cara.
stakx - tidak lagi berkontribusi