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、.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。