79 votes

double? = double? + double?

Je voulais exécuter la commande ping sur StackOverflow de la communauté pour voir si oui ou non je vais perdre mon esprit avec ce peu simple de code C#.

Je suis le développement sur Windows 7, bâtiment de dans ce .NET 4.0, x64 de Débogage.

J'ai le code suivant:

static void Main()
{
    double? y = 1D;
    double? z = 2D;

    double? x;
    x = y + z;
}

Si je débogage et de mettre un point d'arrêt sur la fin accolade, je m'attends à x = 3 dans la Fenêtre et de la Fenêtre d'exécution. x = null à la place.

Si je debug en x86, les choses semblent bien fonctionner. Est quelque chose de mal avec l'x64 compilateur ou est quelque chose de mal avec moi?

86voto

Douglas réponse est correcte sur le JIT de l'optimisation de code mort (à la fois le x86 et le x64 compilateurs). Toutefois, si le compilateur JIT ont été l'optimisation du code mort, il serait immédiatement évident, car x n'apparaît pas dans la fenêtre variables locales. En outre, la montre et immédiate de la fenêtre au lieu de cela vous donne une erreur lorsque vous tentez d'y accéder: "Le nom de" x "n'existe pas dans le contexte actuel". Ce n'est pas ce que vous avez décrit comme passe.

Ce que vous voyez est en fait un bug dans Visual Studio 2010.

Tout d'abord, j'ai essayé de reproduire ce problème sur ma machine principale: Win7x64 et VS2012. Pour .NET 4.0 objectifs, x est égal à 3,0 D lorsqu'elle tombe sur l'accolade fermante. J'ai décidé d'essayer .NET 3.5 cibles ainsi, et à qui, x a également été mis à 3.0 D, pas null.

Puisque je ne peux pas faire une reproduction parfaite de ce problème depuis que j'ai .NET 4.5 est installé sur le dessus de .NET 4.0, j'ai lancé une machine virtuelle et installé VS2010.

Ici, j'ai été en mesure de reproduire le problème. Avec un point d'arrêt sur l'accolade fermante de la Main méthode, à la fois à la fenêtre de surveillance et les habitants de la fenêtre, j'ai vu qu' x a null. C'est là que ça commence à devenir intéressant. J'ai ciblé la v2.0 runtime place et a découvert qu'il était nul il y a trop. Sûrement que ne peut pas être le cas puisque j'ai la même version de l' .NET runtime 2.0 sur mon autre ordinateur qui a réussi à x avec une valeur de 3.0D.

Donc, ce qui se passe, alors? Après quelques recherches autour de windbg, j'ai trouvé le problème:

VS2010 est de vous montrer la valeur de x avant, il a été effectivement affecté.

Je sais que c'est pas à quoi il ressemble, car le pointeur d'instruction est passé l' x = y + z ligne de. Vous pouvez tester vous-même en ajoutant quelques lignes de code à la méthode:

double? y = 1D;
double? z = 2D;

double? x;
x = y + z;

Console.WriteLine(); // Don't reference x here, still leave it as dead code

Avec un point d'arrêt sur l'accolade finale, les habitants et la fenêtre watch montre x comme égal à 3.0D. Toutefois, si vous marchez à travers le code, vous remarquerez que VS2010 ne montre pas x comme étant attribuée jusqu'à ce que , après que vous avez franchi la Console.WriteLine().

Je ne sais pas si ce bug a déjà été signalé à Microsoft vous Connecter, mais vous pouvez le faire, avec ce code comme un exemple. C'est clairement été corrigé dans VS2012 cependant, je ne sais pas si il y aura une mise à jour pour résoudre ce problème ou pas.


Voici ce qui se passe réellement dans le JIT et VS2010

Avec le code d'origine, nous pouvons voir ce que VS est en train de faire et pourquoi c'est mal. Nous pouvons aussi constater que l' x variable n'est pas optimisé loin (sauf si vous avez marqué l'assemblée pour être compilé avec les optimisations activées).

Tout d'abord, regardons la variable locale définitions de l'IL:

.locals init (
    [0] valuetype [mscorlib]System.Nullable`1<float64> y,
    [1] valuetype [mscorlib]System.Nullable`1<float64> z,
    [2] valuetype [mscorlib]System.Nullable`1<float64> x,
    [3] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0000,
    [4] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0001,
    [5] valuetype [mscorlib]System.Nullable`1<float64> CS$0$0002)

C'est normal de sortie en mode de débogage. Visual Studio définit dupliquer les variables locales qui utilise lors des affectations, et puis IL ajoute des commandes pour copier à partir de la CS* variable à l'utilisateur, définies par la variable locale. Ici est le correspondant du code IL qui montre ce qui se passe:

// For the line x = y + z
L_0045: ldloca.s CS$0$0000 // earlier, y was stloc.3 (CS$0$0000)
L_0047: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_004c: conv.r8            // Convert to a double
L_004d: ldloca.s CS$0$0001 // earlier, z was stloc.s CS$0$0001
L_004f: call instance !0 [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
L_0054: conv.r8            // Convert to a double 
L_0055: add                // Add them together
L_0056: newobj instance void [mscorlib]System.Nullable`1<float64>::.ctor(!0) // Create a new nulable
L_005b: nop                // NOPs are placed in for debugging purposes
L_005c: stloc.2            // Save the newly created nullable into `x`
L_005d: ret 

Nous allons faire quelques plus profond de débogage avec WinDbg:

Si vous déboguer l'application dans VS2010 et de laisser un point d'arrêt à la fin de la méthode, nous pouvons joindre WinDbg facilement, non-invasive de la mode.

Voici le calendrier pour l' Main méthode dans la pile d'appel. Nous nous soucions de la propriété intellectuelle (pointeur d'instruction).

0:009> !clrstack
OS Id de Thread: 0x135c (9)
Enfant SP IP Site d'Appel
000000001c48dc00 000007ff0017338d ConsoleApplication1.Programme.Principale(Système D'.String[])
[...]

Si nous considérons le code machine natif pour l' Main méthode, nous pouvons voir que des instructions ont été exécutées à l'époque que VS pauses d'exécution:

000007ff`00173388 e813fe25f2 appel mscorlib_ni+0xd431a0 
 (000007fe'f23d31a0) (Système d'.Nullable`1[[Système.Double, mscorlib]]..ctor(Double), mdToken: 0000000006001ef2)
****000007ff'0017338d cc int 3****
000007ff'0017338e 8d8c2490000000 lea ecx,[rsp+90h]
000007ff`00173395 488b01 mov rax,qword ptr [rcx]
000007ff`00173398 4889842480000000 mov qword ptr [rsp+80h],rax
000007ff'001733a0 488b4108 mov rax,qword ptr [rcx+8]
000007ff'001733a4 4889842488000000 mov qword ptr [rsp+h 88],rax
000007ff'001733ac 488d8c2480000000 lea rcx,[rsp+80h]
000007ff'001733b4 488b01 mov rax,qword ptr [rcx]
000007ff'001733b7 4889442440 mov qword ptr [rsp+40h],rax
000007ff'001733bc 488b4108 mov rax,qword ptr [rcx+8]
000007ff'001733c0 4889442448 mov qword ptr [rsp+48h],rax
000007ff'001733c5 eb00 jmp 000007ff'001733c7
000007ff'001733c7 0f28b424c0000000 movaps xmm6,xmmword ptr [rsp+0C0h]
000007ff'001733cf 4881c4d8000000 ajouter rsp,0D8h
000007ff'001733d6 c3 ret

À l'aide de la propriété intellectuelle que nous avons obtenu à partir d' !clrstack en Main,, nous voyons que l'exécution a été suspendue sur l'instruction directement après l'appel à l' System.Nullable<double>s'constructeur. (int 3 est l'interruption utilisée par les débogueurs pour arrêter l'exécution) j'ai entouré la ligne avec"*", et vous pouvez aussi correspondre à la ligne L_0056 dans l'IL.

La version x64 de l'assemblée qui suit en fait l'affecte à la variable locale x. Notre pointeur d'instruction n'a pas exécuté le code, de sorte que VS2010 est prématurément rompu avant l' x variable a été attribué par le code natif.

EDIT: En x64, l' int 3 enseignement est placé avant le code d'affectation, comme vous pouvez le voir ci-dessus. En x86, que l'instruction est placé après le code d'affectation. Ce qui explique pourquoi VS est la rupture précoce que dans la version x64. Il est difficile de dire si c'est la faute de Visual Studio ou le compilateur JIT. Je ne suis pas sûr de l'application qui insère un point d'arrêt crochets.

30voto

Douglas Points 25145

Le x64 compilateur JIT est connu pour être plus agressif dans ses optimisations que le x86. (Vous pouvez vous référer à "Matrice de Vérification de Limites de l'Élimination dans le CLR" pour un cas où le x86 et le x64 compilateurs générer du code qui est sémantiquement différents.)

Dans ce cas, le x64 est un compilateur de détecter qu' x ne sont jamais lues, et fait disparaître son affectation au total; ceci est connu comme l'élimination du code mort dans l'optimisation du compilateur. Pour éviter cela, il suffit d'ajouter la ligne suivante après la cession:

Console.WriteLine(x);

Vous pouvez observer que non seulement la valeur correcte de l' 3 de papier, mais la variable x d'affichage, la valeur correcte dans le débogueur ainsi (modifier) à la suite de la Console.WriteLine appel à des références.

Edit: Christopher Currens propose une explication alternative pointant vers un bogue dans Visual Studio 2010, ce qui peut être plus précis que le précédent.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X