【问题标题】:Recursion in Windows 7 64 bitWindows 7 64 位中的递归
【发布时间】:2013-07-05 07:40:26
【问题描述】:

我有这个助手类

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”

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

我再说一遍,只有当我午餐时,我才会有这个错误

任何人都可以知道这种行为吗?如果是这样,我需要一些链接来获取更多详细信息。

这是 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 位配置下构建程序,然后转到构建结果的位置并执行程序。 先谢谢了。 我最好的问候。

【问题讨论】:

  • 测试程序对我来说运行良好(Windows 8、.Net 4.5、x64、发布版本)
  • 为什么你需要递归来完成这个任务是一个让我很困扰的问题。
  • 你用 Visual Studio 的程序午餐了吗?如果是的话,它也对我有用。唯一对我不起作用的是当我直接从磁盘午餐exe时。这可能是 Windows 7 中的一个问题。我没有要测试的 Windows 8。 (.Net 4.0)
  • 当然任务不需要任何递归,但我想知道为什么会有这种特殊的行为。
  • @user713248 如果这取决于 Windows 的版本,我会感到惊讶,两者的 CLR 应该相同。

标签: c# .net recursion windows-7-x64 il


【解决方案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](这是存储参数的位置),但他们做错了,所以最终都得到了相同的值。

有趣的是,如果我从您的Main() 中删除对Console.ReadKey() 的调用,代码就可以正常工作,因为对GetMonthDiffrence() 的调用是内联的,并且在这种情况下不会执行尾调用优化。

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

我有submitted this bug on Connect

【讨论】:

  • 他们说他们无法复制它。我会在此处发布此内容,但显然 Microsoft Connect 要求我“登录”尽管已经登录。我只能在应用程序的“平台目标”设置为 x64 时重新制作它。不只是像我认为他们正在做的那样未选中“更喜欢 32 位”
  • 我刚刚在带有 .NET 4.5.1 的 Windows 8.1 Preview 上重现了这个问题 :-(。不幸的是,现在为 4.5.1 一起修复已经太晚了。我会确保我们得到它在下一轮修复。
  • 最后一点:您可以将 [MethodImpl(MethodImplOptions.NoInlining)] 放在 GetMonthDifference 方法之前,这样可以防止内联和尾调用(它位于 System.Runtime.CompilerServices 命名空间中)
猜你喜欢
  • 2011-11-25
  • 2023-03-30
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 1970-01-01
相关资源
最近更新 更多