Windows 7 64位中的递归

本文关键字:递归 64位 Windows | 更新日期: 2023-09-27 18:21:12

我有这个助手类

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

函数计算两个日期之间的月数,它正是我想要的。到目前为止没有问题。

问题是,当我在发布和windows 7 64位时,我总是得到相同的值"0"

当我深入研究这个问题时,我意识到在某个时刻,由于递归调用,这两个参数是相等的。

我重复一遍,只有当我午餐时,我才有这个bug,这个版本是在没有附加到调试器和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位配置上构建proect,然后转到构建结果的位置并执行程序。谢谢你。我最诚挚的问候。

Windows 7 64位中的递归

我可以在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](存储参数的地方),但做得不正确,所以最终都得到了相同的值。

有趣的是,如果我从Main()中删除对Console.ReadKey()的调用,那么代码运行良好,因为对GetMonthDiffrence()的调用是内联的,在这种情况下不会执行尾部调用优化。

一个可能的解决方法是将[MethodImpl(MethodImplOptions.NoInlining)]添加到您的方法中,这似乎会禁用尾部调用优化。

我已经在Connect上提交了这个bug。