web-dev-qa-db-ja.com

多次元配列の列挙値がそれ自体と等しくないのはなぜですか?

考慮してください:

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
    }
}

これはどのように説明できますか? x86 JITで実行している場合、Visual Studio 2015のデバッグビルドで発生します。 x64 JITでリリースビルドまたは実行すると、期待どおりにTrueが出力されます。

コマンドラインから再現するには:

csc Test.cs /platform:x86 /debug

/debug:pdbonly/debug:portableおよび/debug:fullも再現します。

151
shingo

.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ビット整数への必要な変換が行われます。配列ゲッターヘルパー関数は、State.BUGと等しい0x0000000FFを返しました。値を比較するには、-1(0xFFFFFFFF)に変換する必要があります。 MOVSX命令は、Sign eXtension命令です。

013F04CCでも同じことが起こりますが、今回はno MOVSX命令があり、同じ変換を行います。そこでチップが落ち、CMP命令は0xFFFFFFFFと0x000000FFを比較し、それは偽です。したがって、これは省略のエラーであり、コードジェネレーターは、同じsbyteからintへの変換を実行するためにMOVSXを再度発行することに失敗しました。

このバグで特に珍しいのは、オプティマイザーを有効にしたときに正しく機能することです。両方のケースでMOVSXを使用するようになりました。

このバグが長い間検出されなかった理由として、列挙型の基本型としてsbyteが使用されていることが考えられます。非常にまれです。多次元配列を使用することも有用であり、組み合わせは致命的です。

それ以外の場合、私は言うだろうかなり重大なバグ。それがどれほど広範囲に及ぶかは推測するのが難しく、テストするのは4.6.1 x86ジッタだけです。 x64および3.5 x86ジッタは非常に異なるコードを生成し、このバグを回避します。一時的な回避策は、sbyteを列挙の基本型として削除し、デフォルトintにすることです。したがって、符号拡張は不要です。

Connect.Microsoft.comでバグを報告できます。このQ + Aにリンクすれば、知っておく必要があることをすべて伝えることができます。あなたが時間を取りたくない場合は私に知らせてください、私はそれの世話をします。

163
Hans Passant

OPの宣言について考えてみましょう。

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

バグはBUGが負(-128から-1)で、Stateがsigned byteの列挙型である場合にのみ発生するため、どこかにキャストの問題があると推測し始めました。

これを実行する場合:

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

出力されます:

255

-1

バグ

255

無視する理由のために(今のところ) s[0, 0]は評価の前に1バイトにキャストされるため、a == s[0,0]はfalseです。

8
Thomas Ayoub