2

我有这个助手类

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”

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

我再说一遍,只有当我午餐时,我才会有这个错误,因为我没有附加到调试器和 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 位配置下构建项目,然后转到构建结果的位置并执行程序。先谢谢了。我最诚挚的问候。

4

1 回答 1

4

我可以在 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

重要的部分是 4mov条指令。他们尝试交换[rsp+68h]and [rsp+60h](这是存储参数的地方),但他们做错了,所以最终都得到相同的值。

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

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

我已经在 Connect 上提交了这个错误

于 2013-07-05T12:31:45.837 回答