Quería hacer ping a la comunidad de StackOverflow para ver si estoy perdiendo la cabeza o no con este simple código C #.
Estoy desarrollando en Windows 7, construyendo esto en .NET 4.0, depuración x64.
Tengo el siguiente código:
static void Main()
{
    double? y = 1D;
    double? z = 2D;
    double? x;
    x = y + z;
}
Si depuro y pongo un punto de interrupción en la llave final, espero que x = 3 en la Ventana de visualización y la Ventana Inmediato. x = nulo en su lugar.
Si depuro en x86, las cosas parecen funcionar bien. ¿Hay algún problema con el compilador x64 o hay algún problema conmigo?

Respuestas:
La respuesta de Douglas es correcta sobre el código muerto de optimización de JIT ( tanto los compiladores x86 como los x64 harán esto). Sin embargo, si el compilador JIT estuviera optimizando el código muerto, sería inmediatamente obvio porque
xni siquiera aparecería en la ventana Locales. Además, el reloj y la ventana inmediata le darían un error al intentar acceder: "El nombre 'x' no existe en el contexto actual". Eso no es lo que ha descrito que está sucediendo.Lo que está viendo es en realidad un error en Visual Studio 2010.
Primero, intenté reproducir este problema en mi máquina principal: Win7x64 y VS2012. Para los objetivos .NET 4.0,
xes igual a 3.0D cuando se rompe en la llave de cierre. Decidí probar también los destinos .NET 3.5, y con eso,xtambién se configuró en 3.0D, no nulo.Como no puedo hacer una reproducción perfecta de este problema ya que tengo .NET 4.5 instalado sobre .NET 4.0, puse en marcha una máquina virtual e instalé VS2010 en ella.
Aquí, pude reproducir el problema. Con un punto de interrupción en el corchete de cierre del
Mainmétodo, tanto en la ventana del reloj como en la ventana de locales, vi quexeranull. Aquí es donde comienza a ponerse interesante. En su lugar, apunté al tiempo de ejecución v2.0 y descubrí que allí también era nulo. Seguramente ese no puede ser el caso ya que tengo la misma versión del tiempo de ejecución de .NET 2.0 en mi otra computadora que se mostró exitosamentexcon un valor de3.0D.Entonces, ¿qué está pasando entonces? Después de investigar un poco en windbg, encontré el problema:
VS2010 le muestra el valor de x antes de que se le haya asignado .
Sé que no es así, ya que el puntero de instrucción está más allá de la
x = y + zlínea. Puede probar esto usted mismo agregando algunas líneas de código al método:double? y = 1D; double? z = 2D; double? x; x = y + z; Console.WriteLine(); // Don't reference x here, still leave it as dead codeCon un punto de interrupción en la llave final, los lugareños y la ventana de observación se muestran
xcomo iguales a3.0D. Sin embargo, si paso a través del código, usted notará que VS2010 no muestraxcomo asignados hasta después de que hubiera entrado a través de laConsole.WriteLine().No sé si este error se informó alguna vez a Microsoft Connect, pero es posible que desee hacerlo, con este código como ejemplo. Sin embargo, claramente se ha solucionado en VS2012, por lo que no estoy seguro de si habrá una actualización para solucionarlo o no.
Esto es lo que está sucediendo realmente en el JIT y el VS2010
Con el código original, podemos ver qué está haciendo VS y por qué está mal. También podemos ver que el
xvariable no se está optimizando (a menos que haya marcado el ensamblado para que se compile con las optimizaciones habilitadas).Primero, veamos las definiciones de variables locales del 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)Esta es una salida normal en modo de depuración. Visual Studio define variables locales duplicadas que se utilizan durante las asignaciones y luego agrega comandos IL adicionales para copiarlo de la variable CS * a su respectiva variable local definida por el usuario. Aquí está el código IL correspondiente que muestra que esto sucede:
// 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: retHagamos una depuración más profunda con WinDbg:
Si depura la aplicación en VS2010 y deja un punto de interrupción al final del método, podemos adjuntar WinDbg fácilmente, en modo no invasivo.
Aquí está el marco para el
Mainmétodo en la pila de llamadas. Nos preocupamos por la IP (puntero de instrucción).Si vemos el código de máquina nativo del
Mainmétodo, podemos ver qué instrucciones se han ejecutado en el momento en que VS interrumpe la ejecución:000007ff`00173388 e813fe25f2 llamar mscorlib_ni + 0xd431a0 (000007fe`f23d31a0) (System.Nullable`1 [[System.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 + 88h], 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 agregar rsp, 0D8h 000007ff`001733d6 c3 retEl uso de la IP actual que nos dieron de
!clrstackenMain, vemos que la ejecución fue suspendida en la instrucción inmediatamente después de la llamada alSystem.Nullable<double>constructor 's. (int 3es la interrupción utilizada por los depuradores para detener la ejecución) He rodeado esa línea con *, y también puede hacer coincidir la líneaL_0056en el IL.El ensamblado x64 que sigue en realidad lo asigna a la variable local
x. Nuestro puntero de instrucción aún no ha ejecutado ese código, por lo que VS2010 se rompe prematuramente antes de que elxcódigo nativo asigne la variable.EDITAR: En x64, la
int 3instrucción se coloca antes del código de asignación, como puede ver arriba. En x86, esa instrucción se coloca después del código de asignación. Eso explica por qué VS se está rompiendo temprano solo en x64. Es difícil decir si esto es culpa de Visual Studio o del compilador JIT. No estoy seguro de qué aplicación inserta ganchos de puntos de interrupción.fuente
Se sabe que el compilador x64 JIT es más agresivo en sus optimizaciones que el x86. (Puede consultar " Eliminación de comprobación de límites de matriz en CLR " para un caso en el que los compiladores x86 y x64 generan código que es semánticamente diferente).
En este caso, el compilador x64 está detectando que
xnunca se lee y elimina por completo su asignación; esto se conoce como eliminación de código muerto en la optimización del compilador. Para evitar que esto suceda, simplemente agregue la siguiente línea justo después de la tarea:Observará que no solo
3se imprime el valor correcto de , sino que la variablextambién mostraría el valor correcto en el depurador (editar) después de laConsole.WriteLinellamada que lo hace referencia.Editar : Christopher Currens ofrece una explicación alternativa que apunta a un error en Visual Studio 2010, que podría ser más precisa que la anterior.
fuente
Console.WriteLine().Console.WriteLine.xvariable en absoluto. Que no aparecería en la ventana de la población local, y tratando de visualizarla en el reloj o ventanas inmediatos daría el mensaje:The name 'x' does not exist in the current context.