Рекурсия в Windows 7 64 бит

У меня есть этот вспомогательный класс

public static class DateTimeHelper
  {
    public static int GetMonthDiffrence(DateTime date1, DateTime date2)
    {
      if (date1 > date2)
      {
        return getmonthdiffrence(date2, date1);
      }
      else
      {
        return ((date2.year - date1.year) * 12) + (date2.month - date1.month);
      }      
    }
  }

Функция вычисляет количество месяцев между двумя датами, она делает именно то, что я хочу. Пока проблем нет.

Проблема в том, что когда я выпускаю 64-битную версию Windows 7, я всегда получаю одно и то же значение «0».

Когда я углубился в проблему, я понял, что в какой-то момент из-за рекурсивного вызова два параметра равны.

Повторяю, что у меня этот баг возникает только если я ланчу релиз, собранный без привязки к отладчику и на windows 7 64 бит.

Кто-нибудь может иметь представление об этом поведении? И если это так, мне нужно несколько ссылок, чтобы получить более подробную информацию.

Вот код IL. (Я думаю, это может помочь понять больше)

.class public auto ansi abstract sealed beforefieldinit Helpers.DateTimeHelper
    extends [mscorlib]System.Object
{
    // Methods
    .method public hidebysig static 
        int32 GetMonthDiffrence (
            valuetype [mscorlib]System.DateTime date1,
            valuetype [mscorlib]System.DateTime date2
        ) cil managed 
    {
        // Method begins at RVA 0x6a658
        // Code size 52 (0x34)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: call bool [mscorlib]System.DateTime::op_GreaterThan(valuetype [mscorlib]System.DateTime, valuetype [mscorlib]System.DateTime)
        IL_0007: brfalse.s IL_0011

        IL_0009: ldarg.1
        IL_000a: ldarg.0
        IL_000b: call int32 Helpers.DateTimeHelper::GetMonthDiffrence(valuetype [mscorlib]System.DateTime, valuetype [mscorlib]System.DateTime)
        IL_0010: ret

        IL_0011: ldarga.s date2
        IL_0013: call instance int32 [mscorlib]System.DateTime::get_Year()
        IL_0018: ldarga.s date1
        IL_001a: call instance int32 [mscorlib]System.DateTime::get_Year()
        IL_001f: sub
        IL_0020: ldc.i4.s 12
        IL_0022: mul
        IL_0023: ldarga.s date2
        IL_0025: call instance int32 [mscorlib]System.DateTime::get_Month()
        IL_002a: ldarga.s date1
        IL_002c: call instance int32 [mscorlib]System.DateTime::get_Month()
        IL_0031: sub
        IL_0032: add
        IL_0033: ret
    } // end of method DateTimeHelper::GetMonthDiffrence
} 

ИЗМЕНИТЬ:

Вот тестовая программа, если вы хотите воспроизвести проблему:

class Program
  {
    static void Main(string[] args)
    {
      for (int i = 2000; i < 3000; i++)
      {
        var date1 = new DateTime(i, 1, 1);
        var date2 = new DateTime(i + 1, 1, 1);
        var monthdiff = DateTimeHelper.GetMonthDiffrence(date2, date1);
        if (monthdiff == 0)
          Console.WriteLine(string.Format("date1 => {0}, date2 => {1}, diff=> {2}", date2, date1, monthdiff.ToString()));
      }
      Console.WriteLine("done!");
      Console.ReadKey();
    }
  }

вам нужно собрать проект в режиме выпуска и 64-битной конфигурации, а затем перейти к местоположению построенного результата и выполнить программу. Спасибо перед рукой. Мои наилучшие пожелания.


person Swift    schedule 05.07.2013    source источник
comment
Тестовая программа у меня работает нормально (Windows 8, .Net 4.5, x64, релизная сборка)   -  person Matthew Watson    schedule 05.07.2013
comment
Зачем вам нужна рекурсия для этой задачи - вопрос, который меня очень беспокоит.   -  person Ilya Khaustov    schedule 05.07.2013
comment
вы ланчивали программу из visual studio?, если да, то у меня тоже работает. единственный раз, когда он у меня не работает, это когда я запускаю exe прямо с диска. и это может быть проблемой в Windows 7. У меня нет Windows 8 для тестирования. (.Net 4.0)   -  person Swift    schedule 05.07.2013
comment
Конечно, задача не нуждается в рекурсии, но я хочу знать, почему именно такое поведение.   -  person Swift    schedule 05.07.2013
comment
@user713248 user713248 Да, я запускал не из отладчика.   -  person Matthew Watson    schedule 05.07.2013
comment
Спасибо @MatthewWatson, кажется, это проблема с Windows 7.   -  person Swift    schedule 05.07.2013
comment
@user713248 user713248 Я был бы удивлен, если бы это зависело от версии Windows, среда CLR должна быть одинаковой для обоих.   -  person svick    schedule 05.07.2013


Ответы (1)


Я могу воспроизвести это поведение в Windows 7, .Net 4.5, Visual Studio 2012, цели x64, режиме выпуска с подключенным отладчиком, но «Подавить оптимизацию JIT при загрузке модуля» отключен. Кажется, это ошибка в оптимизации хвостового вызова (поэтому вы получаете ее только на x64).

IL здесь не имеет особого значения, имеет значение нативный код. Соответствующая часть кода для GetMonthDiffrence():

0000005e  cmp         rdx,rcx 
00000061  setg        al 
00000064  movzx       eax,al 
00000067  test        eax,eax 
00000069  je          0000000000000081 // else branch
0000006b  mov         rax,qword ptr [rsp+68h] 
00000070  mov         qword ptr [rsp+60h],rax 
00000075  mov         rax,qword ptr [rsp+60h] 
0000007a  mov         qword ptr [rsp+68h],rax 
0000007f  jmp         0000000000000012 // start of the method

Важной частью являются 4 mov инструкции. Они пытаются поменять местами [rsp+68h] и [rsp+60h] (где хранятся параметры), но делают это неправильно, поэтому оба получают одно и то же значение.

Интересно, что если я уберу вызов Console.ReadKey() из вашего Main(), код будет работать нормально, потому что вызов GetMonthDiffrence() встроен и оптимизация хвостового вызова в этом случае не выполняется.

Возможным обходным путем может быть добавление [MethodImpl(MethodImplOptions.NoInlining)] к вашему методу, который, по-видимому, отключает оптимизацию хвостового вызова.

Я отправил сообщение об этой ошибке в Connect .

person svick    schedule 05.07.2013
comment
Сказали, что не могут воспроизвести. Я бы разместил это там, но, видимо, Microsoft Connect требует, чтобы я вошел в систему, несмотря на то, что я уже вошел в систему. Я мог воспроизвести это только тогда, когда целевая платформа приложения была установлена ​​​​на x64. Не просто предпочитаете 32-битные непроверенные, как я предполагаю, что они делают - person Earlz; 11.07.2013
comment
Я только что воспроизвел эту проблему в Windows 8.1 Preview с .NET 4.5.1 :-(. К сожалению, уже слишком поздно, чтобы собрать исправление для 4.5.1. Я позабочусь о том, чтобы мы исправили его в следующем раунде. - person Kevin Frei; 18.07.2013
comment
Последнее замечание: вы можете поместить [MethodImpl(MethodImplOptions.NoInlining)] перед методом GetMonthDifference, что предотвращает как встраивание, так и хвостовой вызов (он находится в пространстве имен System.Runtime.CompilerServices) - person Kevin Frei; 18.07.2013