ทำไมคอมไพเลอร์ C # จึงแปลสิ่งนี้! = การเปรียบเทียบราวกับว่าเป็นการเปรียบเทียบ>


147

ฉันมีโอกาสบริสุทธิ์ค้นพบว่าคอมไพเลอร์ C # เปลี่ยนวิธีนี้:

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

... เข้าสู่CILนี้:

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

…หรือหากคุณต้องการดูรหัส C # ที่ถอดรหัสแล้ว:

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

ทำไมการ!=แปลจึงเป็น " >"

คำตอบ:


201

คำตอบสั้น ๆ :

ไม่มีคำสั่ง "เปรียบเทียบไม่เท่ากับ" ใน IL ดังนั้น!=ผู้ประกอบการC # จึงไม่มีการติดต่อที่แน่นอนและไม่สามารถแปลได้อย่างแท้จริง

มี แต่ "เปรียบเทียบเท่ากับ" การเรียนการสอน ( ceqมีการติดต่อโดยตรงกับ==ผู้ดำเนินการ) ดังนั้นในกรณีทั่วไปได้รับการแปลเช่นอีกเล็กน้อยเทียบเท่าx != y(x == y) == false

นอกจากนี้ยังมีคำสั่ง "เปรียบเทียบมากกว่ามากกว่า" ใน IL ( cgt) ซึ่งอนุญาตให้คอมไพเลอร์ใช้ทางลัดบางอย่าง (เช่นสร้างรหัส IL ที่สั้นกว่า) หนึ่งในนั้นคือการเปรียบเทียบความไม่เท่าเทียมกันของวัตถุกับ null obj != nullได้รับการแปลราวกับว่าพวกเขาเป็น " obj > null"

ลองดูรายละเอียดเพิ่มเติม

หากไม่มีคำสั่ง "เปรียบเทียบไม่เท่ากับ" ใน IL ดังนั้นวิธีต่อไปนี้จะแปลโดยคอมไพเลอร์อย่างไร

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

ดังที่ได้กล่าวแล้วคอมไพเลอร์จะเปลี่ยนx != yเป็น(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
}

ปรากฎว่าคอมไพเลอร์ไม่ได้สร้างลวดลายที่ยืดยาวพอสมควร ลองดูสิ่งที่เกิดขึ้นเมื่อเราแทนที่yด้วยค่าคงที่ 0:

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

IL ที่ผลิตนั้นค่อนข้างสั้นกว่าในกรณีทั่วไป:

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

คอมไพเลอร์สามารถใช้ประโยชน์จากความจริงที่ว่าเลขจำนวนเต็มที่ลงนามนั้นถูกเก็บไว้ในส่วนเสริมของสอง (ซึ่งหากรูปแบบบิตที่ได้รับการตีความเป็นจำนวนเต็มแบบไม่ได้ลงนาม - นั่นคือสิ่งที่.unหมายถึง - 0 มีค่าน้อยที่สุด) ดังนั้นมันแปลx == 0ว่าunchecked((uint)x) > 0.

ปรากฎว่าคอมไพเลอร์สามารถทำเช่นเดียวกันสำหรับการตรวจสอบความไม่เท่าเทียมกันกับnull:

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

คอมไพเลอร์สร้าง IL เกือบจะเหมือนกับIsNotZero:

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

เห็นได้ชัดว่าคอมไพเลอร์ได้รับอนุญาตให้สมมติว่ารูปแบบบิตของการnullอ้างอิงเป็นรูปแบบบิตที่เล็กที่สุดที่เป็นไปได้สำหรับการอ้างอิงวัตถุใด ๆ

ช็อตคัตนี้ถูกกล่าวถึงอย่างชัดเจนในCommon Language Infrastructure Standard (ฉบับที่ 1 จากตุลาคม 2003) (หน้า 491 เป็นเชิงอรรถของตารางที่ 6-4, "การเปรียบเทียบไบนารีหรือการดำเนินการสาขา"):

" cgt.unได้รับอนุญาตและตรวจสอบได้ใน ObjectRefs (O) สิ่งนี้มักใช้เมื่อเปรียบเทียบ ObjectRef กับ null (ไม่มีคำสั่ง" เปรียบเทียบไม่เท่ากัน "ซึ่งจะเป็นทางออกที่ชัดเจนมากขึ้น)


3
คำตอบที่ยอดเยี่ยมเพียงนิดเดียวหนึ่งองค์ประกอบที่สองไม่เกี่ยวข้องที่นี่ มันเป็นเรื่องสำคัญที่ลงนามจำนวนเต็มจะถูกเก็บไว้ในลักษณะที่ว่าค่าที่ไม่ใช่เชิงลบในint's ช่วงมีการแสดงเหมือนกันในขณะที่พวกเขาทำในint uintนั่นเป็นข้อกำหนดที่อ่อนแอกว่าทั้งสองข้อ

3
ประเภทที่ไม่ได้ลงนามไม่เคยมีจำนวนลบใด ๆ ดังนั้นการดำเนินการเปรียบเทียบที่เปรียบเทียบกับศูนย์ไม่สามารถดำเนินการกับหมายเลขที่ไม่ใช่ศูนย์ใด ๆ ที่น้อยกว่าศูนย์ การรับรองทั้งหมดที่เกี่ยวข้องกับค่าที่ไม่เป็นลบของintถูกนำมาใช้โดยค่าเดียวกันในuintดังนั้นการแสดงทั้งหมดที่สอดคล้องกับค่าลบของintต้องสอดคล้องกับค่าบางส่วนที่uintสูงกว่า0x7FFFFFFFแต่ก็ไม่สำคัญว่าค่าใดที่ คือ. (ที่จริงแล้วสิ่งที่จำเป็นจริงๆคือศูนย์จะมีวิธีการเหมือนกันทั้งในintและuint)

3
@hvd: ขอบคุณที่อธิบาย คุณพูดถูกมันไม่ใช่องค์ประกอบสองอย่างที่สำคัญ เป็นข้อกำหนดที่คุณกล่าวถึง และความจริงที่cgt.unปฏิบัติต่อinta uintโดยไม่เปลี่ยนรูปแบบบิตพื้นฐาน (คิดว่าcgt.unครั้งแรกจะพยายามที่จะ underflows แก้ไขโดยการทำแผนที่ตัวเลขติดลบทั้งหมดเป็น 0 ในกรณีที่คุณเห็นได้ชัดว่าไม่สามารถทดแทน> 0สำหรับ!= 0.)
stakx - ไม่เอื้อต่อ

2
ฉันคิดว่ามันน่าประหลาดใจที่การเปรียบเทียบการอ้างอิงวัตถุกับการอ้างอิงอื่นที่ใช้>นั้นเป็นสิ่งที่ตรวจสอบได้ IL ด้วยวิธีนี้เราสามารถเปรียบเทียบวัตถุที่ไม่เป็นโมฆะสองรายการและรับผลบูลีน (ซึ่งไม่ได้กำหนดไว้ล่วงหน้า) นั่นไม่ใช่ปัญหาความปลอดภัยของหน่วยความจำ แต่มันรู้สึกเหมือนการออกแบบที่ไม่สะอาดซึ่งไม่ได้อยู่ในจิตวิญญาณทั่วไปของรหัสที่ได้รับการจัดการอย่างปลอดภัย การออกแบบนี้รั่วความจริงที่ว่าการอ้างอิงวัตถุถูกนำมาใช้เป็นตัวชี้ ดูเหมือนว่าข้อบกพร่องในการออกแบบของ. NET CLI
usr

3
@usr: แน่นอน! ส่วนที่ III.1.1.4 ของมาตรฐาน CLIกล่าวว่า"การอ้างอิงวัตถุ (ประเภท O) นั้นทึบแสงอย่างสมบูรณ์"และ"การดำเนินการเปรียบเทียบที่ได้รับอนุญาตเพียงอย่างเดียวคือความเสมอภาคและความไม่เท่าเทียมกัน ... " บางทีอาจเป็นเพราะการอ้างอิงวัตถุจะไม่ได้กำหนดไว้ในแง่ของการอยู่หน่วยความจำมาตรฐานยังดูแลเพื่อให้แนวคิดการอ้างอิงที่ว่างนอกเหนือจาก 0 (ดูเช่นคำจำกัดความของldnull, initobjและnewobj) ดังนั้นการใช้cgt.unการเปรียบเทียบการอ้างอิงวัตถุกับการอ้างอิงโมฆะจึงปรากฏว่าขัดแย้งกับส่วนที่ III.1.1.4 มากกว่าหนึ่งวิธี
stakx - ไม่ได้มีส่วนร่วมอีกต่อไปใน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.