Если оператор кажется оценивающим, даже когда условие оценивается как ложное

Вчера поздно вечером на работе мы пытались понять, почему что-то не работает. Проверка проверки не удалась, хотя этого не должно было быть.

В итоге мы добавили к этому коду оператор печати (дизассемблированный из Reflector, чтобы проверить, действительно ли код соответствует тому, что мы написали):

public static string Redacted(string name, DateTime lastModified)
{
    long ticks = lastModified.Ticks;
    if ((ticks != (ticks - (ticks % 10000L))) &&
            (lastModified != DateTime.MaxValue))
    {
        Log.Debug(string.Format("Last Modified Date = '{0}'. Ticks = '{1}'. TicksCalc = '{2}'",
            lastModified.ToString("dd/MM/yyyy hh:mm:ss.fff"),
            ticks, ticks - (ticks % 10000L)));

Напечатано (переформатировано):

Last Modified Date = '22/03/2011 12:16:22.000'.
Ticks     = '634363497820000000'.
TicksCalc = '634363497820000000'            

Но условие состоит в том, что "ticks" (что равно Ticks, напечатанному выше) не равно "(ticks - (ticks % 10000))" (что равно TicksCalc)! 634363497820000000 != 634363497820000000?!

Чтобы определить, что здесь происходит, мы добавили еще два утверждения:

long ticks = lastModified.Ticks;
/* Added following two lines: */
long num2 = ticks - (ticks % 10000L);
Log.Debug((ticks == num2).ToString());
/* */
if ((ticks != (ticks - (ticks % 10000L))) &&
        (lastModified != DateTime.MaxValue))
{
    Log.Debug(string.Format("Last Modified Date = '{0}'. Ticks = '{1}'. TicksCalc = '{2}'",
        lastModified.ToString("dd/MM/yyyy hh:mm:ss.fff"),
        ticks, ticks - (ticks % 10000L)));

Как и должно было быть, этот напечатал true (при тестировании с тем же значением) и не написал вторую строку.

Чувствуя себя немного потерянным, мы снова удалили две строки, перекомпилировали и перезапустили. Исходное поведение повторилось.

Сегодня утром я записал видео.

Видео в первую очередь показывает достижение точки останова в методе с использованием «сломанного» кода, а затем перестроение и повторный запуск с использованием «работающего» кода. Обратите внимание, что хотя отладчик показывает, что условие if оценивается как false, тело все равно вводится.

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

Кроме того, это происходит только в режиме Release (т. е. с включенной JIT-оптимизацией).

Вот дизассемблированные методы для обеих версий: работает, не работает. Я не умею читать ассемблер, поэтому публикую их здесь в надежде на разъяснение.

Я надеюсь, что ответ не является чем-то очевидным, что я полностью упустил из виду...!

Редактировать: Вот IL. Я не думаю, что с этим что-то не так, потому что он декомпилируется до правильного C#:

Обновление:

Подтверждено Microsoft как ошибка, исправлено в следующей версии.


person porges    schedule 19.05.2011    source источник
comment
Отличное видео! Такого поведения давно не видел. У меня когда-то была такая же проблема (но на Java). Добавление одного System.out.println(); решил это и для меня. Это действительно странно...   -  person Christian    schedule 19.05.2011
comment
@Porges Можем ли мы получить MSIL (сборку .NET, а не сборку x86 :))? Вы можете использовать .NET Reflector, чтобы получить нужный метод.   -  person pickypg    schedule 25.05.2011
comment
@pickypg: я добавил это. Я не думаю, что с этим что-то не так, поскольку он декомпилируется в правильный код C# (хотя я не проходил его вручную).   -  person porges    schedule 26.05.2011
comment
Не по теме: (ticks != ticks - (ticks % 10000L)) не эквивалентно (ticks % 10000L != 0)?   -  person Phil Gan    schedule 26.05.2011
comment
@ohmantics: не могли бы вы объяснить, почему вы удаляете тег assembly? Я связал код сборки x86, содержащийся в этом вопросе...   -  person porges    schedule 27.05.2011
comment
Я отправил отчет об ошибке в MS Connect: connect.microsoft.com/VisualStudio/feedback/details/671105/   -  person CodesInChaos    schedule 27.05.2011
comment
@Porges Извините, я не видел сборки x86. Очень распространенной ошибкой в ​​SO является пометка .net-assembly как сборки. Я не думаю, что это действительно имеет отношение к вашей проблеме, но тег сборки правильный. Мои извенения.   -  person ohmantics    schedule 28.05.2011


Ответы (5)


Я немного поэкспериментировал с упрощенным кодом: http://nopaste.info/2c99a0e028_nl.html.

Самый интересный вариант:

static readonly long variableZero=0; 
const long constZero=0; 

public static void Broken2( long ticks2) 
 { 
     long ticks = ticks2+variableZero; 
     if (ticks != (ticks - (ticks % 10000L))) 
     { 
         string.Format("Last Modified Date = '{0}'. Ticks = '{1}'. TicksCalc = '{2}'", 
             "n/A", 
             ticks, ticks - (ticks % 10000L)).Dump(); 
     } 
 }

Если я заменю variableZero на constantZero, все сработает.


Так что я почти уверен, что это либо дрожание, либо ошибка компилятора.

Я подал отчет об ошибке в MS Connect: https://connect.microsoft.com/VisualStudio/feedback/details/671105/jitter-or-c-compiler-bug#details


Обновление: странное поведение возникает только в том случае, если отладчик не подключен. т. е. когда Jit-оптимизация включена. Так что я почти уверен, что это ошибка джиттера.

А для тех, у кого нет linq-pad, теперь есть простой консольный проект C#: http://nopaste.info/00a0e37328_nl.html.

person CodesInChaos    schedule 27.05.2011
comment
Вау, спасибо! Я не думал, что это возможно воспроизвести вне контекста нашей библиотеки :) - person porges; 28.05.2011
comment
RE: Обновление - я думал, что упомянул об этом в исходном сообщении, но, похоже, я его отредактировал. Добавлю небольшое замечание. - person porges; 31.05.2011
comment
Они (наконец-то) ответили на ошибку и заявили, что она будет исправлена ​​в следующем выпуске фреймворка. Я проверил еще раз, и, похоже, его нет в последнем выпуске KB2468871/KB2533523, так что, надеюсь, он будет следующим. Отметив это как ответ. - person porges; 18.08.2011

Проверьте эту тему.

Странные операторы If в Visual Studio 2008

Дело в том, что нельзя все время доверять отладчику.

Чтобы «исправить» этот оператор if, добавьте к нему пустой оператор else {}. Отладчик будет работать как положено.

person Razor    schedule 19.05.2011
comment
Такое поведение происходит независимо от того, подключаю ли я отладчик или нет (первый вывод — это запуск без подключенного отладчика). Я просто прикрепил, чтобы иметь возможность пробиться туда и убедиться. - person porges; 19.05.2011
comment
На самом деле я не подключал отладчик до сегодняшнего утра, поэтому первый вывод, исправление и воспроизведение после удаления исправления были без вмешательства отладчика. - person porges; 19.05.2011

Это действительно похоже на - кхм - джиттербаг. Можете ли вы установить точку останова на операторе «если» и показать нам скриншот представления дизассемблирования после того, как он сработает?

person 500 - Internal Server Error    schedule 25.05.2011
comment
Так и есть - извините. К сожалению, сейчас у меня нет времени анализировать, но завтра еще раз посмотрю, если ни у кого нет. - person 500 - Internal Server Error; 26.05.2011

У меня было что-то подобное некоторое время назад. В моем случае это было связано с тем, что я сравнивал 2 целочисленных значения, где одно значение было на самом деле ссылкой на целое число в штучной упаковке, а другое было реальным примитивным значением.

Дело в том, что если вы распечатаете значение коробочного целого числа и примитива, они будут выглядеть одинаково, но сравнивать их — это другое дело. Вместо сравнения значений вы получите эталонное сравнение.

Ответ прост:

long ticks = lastModified.Ticks;
long num2 = ticks - (ticks % 10000L);
if ((ticks != num2) && (lastModified != DateTime.MaxValue))
{ do your thing here! }
person RogierBessem    schedule 26.05.2011

Это безумие. Пытались ли вы без веской причины изменить порядок оператора if?

if (lastModified != DateTime.MaxValue && ticks != (ticks - (ticks % 10000L))

Кроме того, если это не работает (как и не должно, учитывая, что в первую очередь это не должно быть проблемой), можете ли вы показать фактический IL для кода в проблемной форме?

Еще одна вещь, нельзя ли упростить проверку ticks до:

(ticks % 10000L) != 0
person pickypg    schedule 20.05.2011