Considérez le code suivant:
private static void Main(string[] args)
{
var ar = new double[]
{
100
};
FillTo(ref ar, 5);
Console.WriteLine(string.Join(",", ar.Select(a => a.ToString()).ToArray()));
}
public static void FillTo(ref double[] dd, int N)
{
if (dd.Length >= N)
return;
double[] Old = dd;
double d = double.NaN;
if (Old.Length > 0)
d = Old[0];
dd = new double[N];
for (int i = 0; i < Old.Length; i++)
{
dd[N - Old.Length + i] = Old[i];
}
for (int i = 0; i < N - Old.Length; i++)
dd[i] = d;
}
Le résultat en mode débogage est: 100,100,100,100,100. Mais en mode Release c'est: 100,100,100,100,0.
Qu'est-ce qui se passe?
Il a été testé avec .NET Framework 4.7.1 et .NET Core 2.0.0.
Console.WriteLine(i);
dans la boucle finale (dd[i] = d;
) le "corrige", ce qui suggère un bogue du compilateur ou un bogue JIT; looking into the IL ...if (dd.Length >= N) return;
, ce qui peut être une repro plus simple.Réponses:
Cela semble être un bogue JIT; J'ai testé avec:
// ... existing code unchanged for (int i = 0; i < N - Old.Length; i++) { // Console.WriteLine(i); // <== comment/uncomment this line dd[i] = d; }
et en ajoutant les
Console.WriteLine(i)
correctifs. Le seul changement IL est:// ... L_0040: ldc.i4.0 L_0041: stloc.3 L_0042: br.s L_004d L_0044: ldarg.0 L_0045: ldind.ref L_0046: ldloc.3 L_0047: ldloc.1 L_0048: stelem.r8 L_0049: ldloc.3 L_004a: ldc.i4.1 L_004b: add L_004c: stloc.3 L_004d: ldloc.3 L_004e: ldarg.1 L_004f: ldloc.0 L_0050: ldlen L_0051: conv.i4 L_0052: sub L_0053: blt.s L_0044 L_0055: ret
contre
// ... L_0040: ldc.i4.0 L_0041: stloc.3 L_0042: br.s L_0053 L_0044: ldloc.3 L_0045: call void [System.Console]System.Console::WriteLine(int32) L_004a: ldarg.0 L_004b: ldind.ref L_004c: ldloc.3 L_004d: ldloc.1 L_004e: stelem.r8 L_004f: ldloc.3 L_0050: ldc.i4.1 L_0051: add L_0052: stloc.3 L_0053: ldloc.3 L_0054: ldarg.1 L_0055: ldloc.0 L_0056: ldlen L_0057: conv.i4 L_0058: sub L_0059: blt.s L_0044 L_005b: ret
qui semble tout à fait correct (la seule différence est le supplément
ldloc.3
etcall void [System.Console]System.Console::WriteLine(int32)
, et une cible différente mais équivalente pourbr.s
).Il faudra un correctif JIT, je suppose.
Environnement:
Environment.Version
: 4.0.30319.42000<TargetFramework>netcoreapp2.0</TargetFramework>
dotnet --version
: 2.1.1la source
C'est en effet une erreur d'assemblage. x64, .net 4.7.1, version de version.
démontage:
for(int i = 0; i < N - Old.Length; i++) 00007FF942690ADD xor eax,eax for(int i = 0; i < N - Old.Length; i++) 00007FF942690ADF mov ebx,esi 00007FF942690AE1 sub ebx,ebp 00007FF942690AE3 test ebx,ebx 00007FF942690AE5 jle 00007FF942690AFF dd[i] = d; 00007FF942690AE7 mov rdx,qword ptr [rdi] 00007FF942690AEA cmp eax,dword ptr [rdx+8] 00007FF942690AED jae 00007FF942690B11 00007FF942690AEF movsxd rcx,eax 00007FF942690AF2 vmovsd qword ptr [rdx+rcx*8+10h],xmm6 for(int i = 0; i < N - Old.Length; i++) 00007FF942690AF9 inc eax 00007FF942690AFB cmp ebx,eax 00007FF942690AFD jg 00007FF942690AE7 00007FF942690AFF vmovaps xmm6,xmmword ptr [rsp+20h] 00007FF942690B06 add rsp,30h 00007FF942690B0A pop rbx 00007FF942690B0B pop rbp 00007FF942690B0C pop rsi 00007FF942690B0D pop rdi 00007FF942690B0E pop r14 00007FF942690B10 ret
Le problème se trouve à l'adresse 00007FF942690AFD, le jg 00007FF942690AE7. Il saute en arrière si ebx (qui contient 4, la valeur de fin de boucle) est plus grand (jg) que eax, la valeur i. Cela échoue quand il est 4 bien sûr, donc il n'écrit pas le dernier élément du tableau.
Il échoue, car il incorpore la valeur de registre i (eax, à 0x00007FF942690AF9), puis le vérifie avec 4, mais il doit toujours écrire cette valeur. Il est un peu difficile de localiser exactement le problème, car il semble que cela pourrait être le résultat de l'optimisation de (N-Old.Length), car la version de débogage contient ce code, mais la version de version précalcule cela. C'est donc aux gens de jit de réparer;)
la source