ทำไมค่าการแจงนับจากอาร์เรย์หลายมิติจึงไม่เท่ากัน?


151

พิจารณา:

using System;

public class Test
{
    enum State : sbyte { OK = 0, BUG = -1 }

    static void Main(string[] args)
    {
        var s = new State[1, 1];
        s[0, 0] = State.BUG;
        State a = s[0, 0];
        Console.WriteLine(a == s[0, 0]); // False
    }
}

จะอธิบายได้อย่างไร? มันเกิดขึ้นในการตรวจแก้จุดบกพร่องสร้างใน Visual Studio 2015 เมื่อทำงานใน x86 JIT รุ่นวางจำหน่ายหรือทำงานใน x64 JIT พิมพ์จริงตามที่คาดไว้

ในการทำซ้ำจากบรรทัดคำสั่ง:

csc Test.cs /platform:x86 /debug

( /debug:pdbonly, /debug:portableและ/debug:fullนอกจากนี้ยังมีการทำซ้ำ.)


2
ideone.com/li3EzYมันเป็นเรื่องจริง เพิ่มข้อมูลเพิ่มเติมเกี่ยวกับ. net รุ่น, IDE, คอมไพเลอร์
หลัง

1
กันที่นี่ แต่หลังจากที่เล่นซอกับการตั้งค่าโครงการฉันก็พบว่าการยกเลิกการเลือกช่องทำเครื่องหมาย "ชอบ 32 บิต" ในแท็บ "สร้าง" ทำให้มันทำงานได้ตามที่ตั้งใจไว้ - คืนค่าจริง ดังนั้นดูเหมือนว่าจะเป็นปัญหา WoW64 สำหรับฉัน
Dmitry Rotay

2
ดูเหมือนว่าคุณจะชี้จุดบกพร่องในกรอบ
Fabien PERRONNET

1
ที่น่าสนใจใช้รหัสผ่านเสียildasmแล้วilasm"แก้ไข" มัน ...
จอนสกีต

2
/debug=IMPLใบธงมันเสีย; /debug=OPT"แก้ไข" มัน
Jon Skeet

คำตอบ:


163

คุณพบข้อผิดพลาดในการสร้างรหัสใน jitter .NET 4 x86 มันเป็นสิ่งที่ผิดปกติมากมันจะล้มเหลวก็ต่อเมื่อรหัสไม่ได้รับการปรับให้เหมาะสม รหัสเครื่องมีลักษณะดังนี้:

        State a = s[0, 0];
013F04A9  push        0                            ; index 2 = 0
013F04AB  mov         ecx,dword ptr [ebp-40h]      ; s[] reference
013F04AE  xor         edx,edx                      ; index 1 = 0
013F04B0  call        013F0058                     ; eax = s[0, 0]
013F04B5  mov         dword ptr [ebp-4Ch],eax      ; $temp1 = eax 
013F04B8  movsx       eax,byte ptr [ebp-4Ch]       ; convert sbyte to int
013F04BC  mov         dword ptr [ebp-44h],eax      ; a = s[0, 0]
        Console.WriteLine(a == s[0, 0]); // False
013F04BF  mov         eax,dword ptr [ebp-44h]      ; a
013F04C2  mov         dword ptr [ebp-50h],eax      ; $temp2 = a
013F04C5  push        0                            ; index 2 = 0
013F04C7  mov         ecx,dword ptr [ebp-40h]      ; s[] reference 
013F04CA  xor         edx,edx                      ; index 1 = 0
013F04CC  call        013F0058                     ; eax = s[0, 0]
013F04D1  mov         dword ptr [ebp-54h],eax      ; $temp3 = eax 
                                               ; <=== Bug here!
013F04D4  mov         eax,dword ptr [ebp-50h]      ; a == s[0, 0] 
013F04D7  cmp         eax,dword ptr [ebp-54h]  
013F04DA  sete        cl  
013F04DD  movzx       ecx,cl  
013F04E0  call        731C28F4  

ความสัมพันธ์ที่เต็มไปด้วยจำนวนชั่วคราวและการทำสำเนารหัสเป็นเรื่องปกติสำหรับรหัสที่ไม่ได้เพิ่มประสิทธิภาพ คำสั่งที่ 013F04B8 นั้นโดดเด่นนั่นคือการแปลงที่จำเป็นจาก sbyte เป็นจำนวนเต็ม 32 บิต ฟังก์ชันตัวช่วยสร้าง getter ของอาร์เรย์ส่งคืน 0x0000000FF เท่ากับ State.BUG และต้องแปลงเป็น -1 (0xFFFFFFFF) ก่อนที่จะสามารถเปรียบเทียบค่าได้ คำสั่ง MOVSX เป็นคำสั่ง Sign eXtension

สิ่งเดียวกันเกิดขึ้นอีกครั้งที่ 013F04CC แต่คราวนี้ไม่มีคำสั่ง MOVSX ที่จะทำการแปลงเดียวกัน นั่นคือสิ่งที่ชิปล้มลงคำสั่ง CMP เปรียบเทียบ 0xFFFFFFFF กับ 0x000000FF และนั่นเป็นเท็จ ดังนั้นนี่คือข้อผิดพลาดของการละเว้นตัวสร้างโค้ดล้มเหลวในการปล่อย MOVSX อีกครั้งเพื่อดำเนินการ sbyte เดียวกันกับการแปลง int

สิ่งที่ผิดปกติอย่างยิ่งเกี่ยวกับข้อผิดพลาดนี้คือมันทำงานได้อย่างถูกต้องเมื่อคุณเปิดใช้งานเครื่องมือเพิ่มประสิทธิภาพตอนนี้ก็รู้ว่าจะใช้ MOVSX ในทั้งสองกรณี

สาเหตุที่เป็นไปได้ที่ข้อผิดพลาดนี้ตรวจไม่พบเป็นเวลานานเนื่องจากการใช้ sbyte เป็นประเภทฐานของ enum ค่อนข้างหายากที่จะทำ การใช้อาเรย์หลายมิตินั้นมีประโยชน์เช่นกันการรวมกันนั้นเป็นอันตรายถึงชีวิต

มิฉะนั้นเป็นข้อผิดพลาดที่สำคัญสวยที่ฉันพูด มันอาจจะยากที่จะคาดเดาได้ว่าฉันจะมีแค่ 4.6.1 x86 jitter ที่จะทำการทดสอบเท่านั้น x64 และ jitter 3.5 x86 สร้างรหัสที่แตกต่างกันมากและหลีกเลี่ยงข้อผิดพลาดนี้ วิธีแก้ปัญหาชั่วคราวเพื่อดำเนินการต่อไปคือการลบ sbyte เป็นประเภทฐาน enum และปล่อยให้เป็นค่าเริ่มต้นintดังนั้นจึงไม่จำเป็นต้องมีส่วนขยายสัญญาณ

คุณสามารถยื่นข้อผิดพลาดได้ที่ connect.microsoft.com การลิงก์ไปยัง Q + A นี้น่าจะเพียงพอที่จะบอกพวกเขาทุกสิ่งที่พวกเขาจำเป็นต้องรู้ แจ้งให้เราทราบหากคุณไม่ต้องการใช้เวลาและฉันจะดูแลมัน


33
ข้อมูลที่ดีและมั่นคงพร้อมสาเหตุที่แท้จริงของปัญหาแปลก ๆ และมีความสุขเสมอที่ได้อ่าน +1
Lasse V. Karlsen

11
โปรดโพสต์ลิงก์ไปยังบทความ connect.microsoft.com เพื่อให้เราสามารถลงคะแนนได้
ฮันส์ Passant

ฉันถือว่าใช้byteแทนsbyteควรจะดีเช่นกันและอาจจะดีกว่าถ้าใช้รหัสจริงกับ ORM ที่คุณไม่ต้องการให้ค่าสถานะของคุณในฐานข้อมูลใช้พื้นที่เพิ่มขึ้น
Voo

6
ฉันจะโพสต์ข้อผิดพลาดบน dotnet / coreclrแทนที่จะเชื่อมต่อคุณจะไปที่ JIT devs โดยตรง
Lucas Trzesniewski

8
ฉันเป็นนักพัฒนาทีม JIT ที่ Microsoft ฉันทำซ้ำข้อผิดพลาดและได้เปิดปัญหาให้กับมันเป็นการภายใน (การจัดส่ง x86 JIT ยังไม่ได้เปิดใน github) ในแง่ของระยะเวลาที่จะแก้ไขปัญหานี้ฉันคาดหวังว่าเราจะมีการแก้ไขนี้รวมอยู่ในเครื่องมือรุ่นใหญ่ครั้งต่อไป หากข้อผิดพลาดนี้มีผลกระทบทางธุรกิจและคุณต้องแก้ไขก่อนหน้านี้โปรดส่งปัญหาการเชื่อมต่อ (connect.microsoft.com) เพื่อให้เราสามารถดูผลกระทบและทางเลือกอื่นที่เราต้องแก้ไขให้คุณได้เร็วขึ้น
Russell C. Hadley

8

ลองพิจารณาคำประกาศของ OP:

enum State : sbyte { OK = 0, BUG = -1 }

เนื่องจากข้อผิดพลาดจะเกิดขึ้นBUGก็ต่อเมื่อเป็นค่าลบ (จาก -128 ถึง -1) และสถานะเป็น enum ของไบต์ที่เซ็นชื่อฉันเริ่มคิดว่ามีปัญหาการส่ง

หากคุณเรียกใช้สิ่งนี้:

Console.WriteLine((sbyte)s[0, 0]);
Console.WriteLine((sbyte)State.BUG);
Console.WriteLine(s[0, 0]);
unchecked
{
    Console.WriteLine((byte) State.BUG);
}

มันจะเอาท์พุท:

255

-1

BUG

255

ด้วยเหตุผลที่ฉันไม่สนใจ(ณ ตอนนี้) s[0, 0]ถูกส่งไปยังไบต์ก่อนการประเมินผลและนั่นเป็นเหตุผลที่มันอ้างว่าa == s[0,0]เป็นเท็จ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.