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
x
ni 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,
x
es igual a 3.0D cuando se rompe en la llave de cierre. Decidí probar también los destinos .NET 3.5, y con eso,x
tambié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
Main
método, tanto en la ventana del reloj como en la ventana de locales, vi quex
eranull
. 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ó exitosamentex
con 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 + z
lí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 code
Con un punto de interrupción en la llave final, los lugareños y la ventana de observación se muestran
x
como iguales a3.0D
. Sin embargo, si paso a través del código, usted notará que VS2010 no muestrax
como 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
x
variable 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: ret
Hagamos 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
Main
mé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
Main
método, podemos ver qué instrucciones se han ejecutado en el momento en que VS interrumpe la ejecución:El uso de la IP actual que nos dieron de
!clrstack
enMain
, vemos que la ejecución fue suspendida en la instrucción inmediatamente después de la llamada alSystem.Nullable<double>
constructor 's. (int 3
es 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_0056
en 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 elx
código nativo asigne la variable.EDITAR: En x64, la
int 3
instrucció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
x
nunca 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
3
se imprime el valor correcto de , sino que la variablex
también mostraría el valor correcto en el depurador (editar) después de laConsole.WriteLine
llamada 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
.x
variable 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
.