问题
I have this helper class
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);
}
}
}
The function calculate the number of months between two dates, it do exactly what I want. So far there is no problem.
The problem is when I am on release and windows 7 64 bit I get always the same value "0"
When I got deep down the problem, I got aware that at some point, and because of the recursive call the two parameters are equal.
I repeat that I have this bug only if I lunch the release built un-attached to the debugger and on windows 7 64 bit.
Can anyone have any idea of this comportment? And if so I need some links to get in more details.
Here is the IL Code. (I think it can help to understand more)
.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
}
EDIT:
Here is a test program if you wish to reproduce the issue:
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();
}
}
you have to build the progect on release mode and 64 bit configuration, and then go to the location of the built result and execute the program. Thanks before hand. My best regards.
回答1:
I can replicate this behavior on Windows 7, .Net 4.5, Visual Studio 2012, x64 target, Release mode with debugger attached, but “Suppress JIT optimization on module load” disabled. This seems to be a bug in the tail call optimization (which is why you're getting it only on x64).
IL does not really matter here, the native code does. The relevant part of code for GetMonthDiffrence()
is:
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
The important part are the 4 mov
instructions. They try to exchange [rsp+68h]
and [rsp+60h]
(which is where the parameters are stored), but they do it incorrectly, so both end up with the same value.
Interestingly, if I remove the call to Console.ReadKey()
from your Main()
, the code works fine, because the call to GetMonthDiffrence()
is inlined and the tail call optimization is not performed in that case.
A possible workaround would be to add [MethodImpl(MethodImplOptions.NoInlining)]
to your method, that seems to disable the tail call optimization.
I have submitted this bug on Connect.
来源:https://stackoverflow.com/questions/17483585/recursion-in-windows-7-64-bit