Почему значение перечисления из многомерного массива не равно самому себе?


Рассмотрим:

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 печатает True, как и ожидалось.

Для воспроизведения из командной строки:

csc Test.cs /platform:x86 /debug

(/debug:pdbonly, /debug:portable и /debug:full также размножаются.)

2 151

2 ответа:

Вы нашли ошибку генерации кода в .NET 4 x86 jitter. Это очень необычный метод, он дает сбой только тогда, когда код не оптимизирован. Машинный код выглядит так:

        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-разрядное целое число. Массив вспомогательную функцию добытчика вернулся 0x0000000FF, равна государства.Жучок, и все такое необходимо преобразовать в -1 (0xffffffffff), прежде чем значение можно будет сравнить. Инструкция MOVSX-это инструкция расширения знака.

То же самое происходит снова в 013F04CC, но на этот раз нет Инструкции MOVSX для выполнения того же преобразования. Вот где фишки падают, инструкция CMP сравнивает 0xFFFFFFFF с 0x000000FF, и это ложь. Так что это ошибка пропускания, генератор кода не смог снова выдать MOVSX для выполнения того же sbyte к int преобразование.

Что особенно необычно в этой ошибке, это то, что она работает правильно, когда вы включаете оптимизатор, теперь он знает, чтобы использовать MOVSX в обоих случаях.

Вероятной причиной того, что эта ошибка оставалась незамеченной так долго, является использование sbyte в качестве базового типа перечисления. Довольно редкое занятие. Использование многомерного массива также играет важную роль, комбинация фатальна.

В противном случае довольно критическая ошибка, Я бы сказал. Насколько широко это может быть распространено, трудно догадаться, я есть только 4.6.1 x86 джиттер для тестирования. X64 и 3.5 x86 jitter генерируют очень разный код и избегают этой ошибки. В качестве временного решения проблемы, чтобы продолжать идти, чтобы удалить тип sbyte в качестве базового перечислимого типа и пусть это будет по умолчанию, инт, так нет знаковое расширение необходимо.

Вы можете подать сообщение об ошибке по адресу connect.microsoft.com, привязки к этому Q+A должно быть достаточно, чтобы рассказать им все, что им нужно знать. Дайте мне знать, если вы не хотите тратить время, и я позабочусь об этом.

Рассмотрим заявление ОП:

enum State : sbyte { OK = 0, BUG = -1 }
Так как ошибка возникает только тогда, когда BUG отрицательно (от -128 до -1) и состояние является перечислением подписанного байта, я начал предполагать, что где-то есть проблема приведения.

Если вы выполните это:

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] приводится к байту перед вычислением, и вот почему он утверждает, что a == s[0,0] является ложным.