2009-05-18 10 views
5

¿Alguien puede explicar estas pocas líneas de MSIL? ¿Por qué mueve un valor fuera de la pila de evaluación a una variable local, solo para moverlo de regreso inmediatamente y devolverlo?¿Puede alguien explicar estas pocas líneas de MSIL?

El siguiente código MSIL carga un único argumento (una cadena), llama a un método, que devuelve bool, y luego devuelve ese valor bool. Lo que no entiendo es por qué llama a stloc.0 para almacenar el valor de retorno del método en una variable local, luego realiza una transferencia de control incondicional explícita a la próxima línea etiquetada (parece innecesario), solo para mover el valor de nuevo a la pila de evaluación antes de devolverla.

.maxstack 1 
.locals init ([0] bool CS$1$0000) 
L_0000: nop 
L_0001: ldarg.0 
L_0002: call bool FuncNameNotImporant::MethodNameNotImporant(string) 
L_0007: stloc.0 
L_0008: br.s L_000a 
L_000a: ldloc.0 
L_000b: ret 

Mi mejor conjetura por qué hace esto es para llevar a cabo algún tipo de comprobación de tipos para asegurar el valor en la pila de evaluación es en realidad un valor booleano antes de devolverlo. Pero no tengo ni idea del salto explícito a la siguiente línea; Quiero decir, ¿no iría allí de todos modos? El código fuente de C# para el método es solo una línea, que devuelve el resultado del método.

Respuesta

7

Si abre esta función en el depurador, con el código compilado en modo de depuración:

bool foo(string arg) 
{ 
    return bar(arg); 
} 

Hay 3 puntos de quiebre que puede establecer:

  1. En la llave de apertura de la función.
  2. En la línea de "devolución".
  3. En la llave de cierre de la función.

Establecer un punto de interrupción en la abrazadera de apertura significa "romper cuando se llame a esta función". Es por eso que hay una instrucción de no-operación al comienzo del método. Cuando el punto de interrupción se establece en la llave de apertura, el depurador realmente lo establece en la operación no operativa.

Establecer un punto de interrupción en la abrazadera de cierre significa "romper cuando esta función finaliza". Para que esto suceda, la función necesita tener una sola instrucción de retorno en su IL, donde se puede establecer el punto de ruptura. El compilador permite que mediante el uso de una variable temporal para almacenar el valor de retorno, y la conversión de

return retVal; 

en

$retTmp = retVal; 
goto exit; 

y luego inyectar el siguiente código en la parte inferior del método:

exit: 
return $ret; 

Además, cuando se encuentran en modo de depuración, los compiladores son estúpidos con respecto al código que generan.Básicamente, hacer algo como:

GenerateProlog(); 
foreach (var statement in statements) 
{ 
    Generate(statement); 
} 
GenerateEpilog(); 

En su caso, que está viendo:

return foo(arg); 

que se traduce en:

; //this is a no-op 
bool retTemp = false; 
retTemp = foo(arg); 
goto exit; 
exit: 
return retTemp; 

Si el compilador estaba haciendo una "optimización de ventana deslizante" podría ser capaz de mirar ese código y darse cuenta de que había cierta redundancia. Sin embargo, los compiladores generalmente no lo hacen en modo de depuración. Las optimizaciones del compilador pueden hacer cosas como eliminar variables y reordenar instrucciones, lo que dificulta la depuración. Como el propósito de una compilación de depuración es habilitar la depuración, no sería bueno activar las optimizaciones.

En una compilación de versión, el código no se verá así. Esto se debe a que el compilador no introduce el código especial para permitir puntos de interrupción en las llaves de apertura y cierre, que sólo deja la siguiente para ser compilado:

return bar(arg); 

que termina pareciendo bastante simple.

Una cosa a tener en cuenta, sin embargo, es que no creo que el compilador C# haga muchas optimizaciones de ventanas deslizantes, incluso en construcciones minoristas. Eso es porque la mayoría de esas optimizaciones dependen de la arquitectura del procesador subyacente y, por lo tanto, las realiza el compilador JIT. Hacer las optimizaciones, incluso las que son independientes del procesador, en el compilador de C# puede impedir la capacidad del JIT de optimizar el código (busca patrones generados por la generación de código no optimizado, y si ve una IL altamente optimizada, puede obtener confuso). Por lo general, los compiladores de código gestionados no los hacen. Hace algunas "cosas costosas" (que el JIT no quiere hacer en tiempo de ejecución), como la detección de código muerto y el análisis de variables activas, pero no resuelven los problemas resueltos mediante la optimización de ventanas deslizantes.

4

¿Está compilando en modo depuración o versión? En el modo de disparo consigo:

.method private hidebysig static bool Test1(string arg) cil managed 
{ 
    .maxstack 8 
    L_0000: ldarg.0 
    L_0001: call bool FuncNameNotImportant::MethodNameNotImportant(string) 
    L_0006: ret 
} 

La ramificación que se está viendo es, probablemente, para el apoyo depurador.

+0

suena razonable para mí –

Cuestiones relacionadas