2009-05-18 11 views
5

Qualcuno può spiegare queste poche righe di MSIL? Perché sposta un valore dallo stack di valutazione a una variabile locale, solo per spostarlo immediatamente e restituirlo?Qualcuno può spiegare queste poche righe di MSIL?

Il seguente codice MSIL carica un singolo argomento (una stringa), chiama un metodo, che restituisce bool e quindi restituisce tale valore bool. Quello che non capisco è il motivo per cui chiama stloc.0 per memorizzare il valore di ritorno del metodo in una variabile locale, quindi esegue un trasferimento di controllo incondizionato esplicito alla riga etichettata più vicina (sembra inutile), solo per spostare nuovamente il valore su lo stack di valutazione prima di restituirlo.

.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 

La mia ipotesi migliore al motivo per cui lo fa è quello di eseguire una sorta di controllo di tipo al fine di garantire il valore sullo stack di valutazione è in realtà un valore booleano prima di restituirlo. Ma non ho idea del salto esplicito verso la linea successiva; Voglio dire, non andrebbe comunque lì? Il codice sorgente C# per il metodo è solo una riga, che restituisce il risultato del metodo.

risposta

7

Se si apre questa funzione nel debugger, con il codice compilato in modalità debug:

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

Ci sono 3 punti di rottura che si possono impostare:

  1. Alla coppia della funzione di apertura.
  2. Sulla riga "ritorno".
  3. Alla parentesi di chiusura della funzione.

L'impostazione di un punto di interruzione nella parentesi di apertura significa "interrompi quando viene chiamata questa funzione". Ecco perché c'è un'istruzione no-op all'inizio del metodo. Quando il punto di interruzione è impostato sulla parentesi di apertura, il debugger lo imposta effettivamente sul no-op.

L'impostazione di un punto di interruzione sulla parentesi di chiusura significa "interruzione al termine di questa funzione". Affinché questo accada, la funzione deve avere un'unica istruzione di ritorno nel suo IL, dove è possibile impostare il punto di interruzione. Il compilatore permette che utilizzando una variabile temporanea per memorizzare il valore di ritorno, e la conversione

return retVal; 

in

$retTmp = retVal; 
goto exit; 

e poi iniettando il seguente codice nella parte inferiore del metodo:

exit: 
return $ret; 

Inoltre, in modalità di debug, i compilatori sono stupidi riguardo al codice che generano.In sostanza fare qualcosa di simile:

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

Nel tuo caso, si sta vedendo:

return foo(arg); 

essere tradotto in:

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

Se il compilatore stava facendo un "ottimizzazione finestra scorrevole" potrebbe essere in grado di guardare quel codice e capire che c'era qualche ridondanza. Tuttavia, i compilatori generalmente non lo fanno in modalità di debug. Le ottimizzazioni del compilatore possono fare cose come eliminare le variabili e riordinare le istruzioni, il che rende difficile il debug. Poiché lo scopo di una build di debug è di abilitare il debug, non sarebbe opportuno attivare le ottimizzazioni.

In una versione di rilascio, il codice non sarà così. Questo perché il compilatore non introdurre il codice speciale per consentire punti di interruzione sulle bretelle di apertura e chiusura, che lascia solo la seguente da compilare:

return bar(arg); 

che finisce per guardare piuttosto semplice.

Una cosa da notare, tuttavia, è che non penso che il compilatore C# faccia molta ottimizzazione delle finestre scorrevoli, anche nelle versioni di vendita al dettaglio. Questo perché la maggior parte di queste ottimizzazioni dipendono dall'architettura del processore sottostante e quindi vengono eseguite dal compilatore JIT. Fare le ottimizzazioni, anche quelle che sono indipendenti dal processore, nel compilatore C# può impedire la capacità del JIT di ottimizzare il codice (è alla ricerca di schemi generati dalla generazione di codice non ottimizzata, e se vede un IL ottimizzato può ottenere confuso). Di solito, i compilatori di codice manipolati non li fanno. Fa alcune "cose ​​costose" (che il JIT non vuole fare in fase di runtime), come il rilevamento del codice morto e l'analisi delle variabili in tempo reale, ma non risolvono i problemi risolti dall'ottimizzazione della finestra scorrevole.

4

Si sta compilando in modalità di debug o di rilascio? In modalità di rilascio ottengo:

.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 ramificazione che stai vedendo è probabilmente per il supporto debugger.

+0

suoni ragionevoli per me –

Problemi correlati