Come modificare il codice in fase di esecuzione in un interprete?

0

Durante la lettura della differenza tra compilatore e interprete, ho trovato le seguenti differenze da internet.

Vantaggi dell'uso del compilatore:

  • Poiché il compilatore converte il programma in codice nativo della macchina di destinazione (codice oggetto), è possibile prevedere prestazioni più veloci.
  • Esiste un ambito per l'ottimizzazione del codice.

Vantaggi dell'uso dell'interprete:

  • Il processo di esecuzione può essere eseguito in un'unica fase. Non c'è bisogno di una fase di compilazione.
  • Alterazione dei codici possibili durante il runtime.
  • Molto utile per il debug dei codici (perché l'esecuzione del codice sorgente può essere analizzata in un IDE)
  • Facilita lo sviluppo di codice interattivo.

In caso di interprete, dichiarato come interprete "c'è la possibilità di alterare il codice" e

In caso di compilatore "l'ottimizzazione del codice sarà lì"

Qualcuno può spiegare questi 2 punti?

    
posta user2720323 13.12.2013 - 07:37
fonte

6 risposte

5

Nota a margine: usa attentamente la distinzione compilatore / interprete, poiché ha i suoi avvertimenti.

  • Alcuni fanno uso di questa distinzione per affermare che la loro lingua è più veloce perché è compilata / interpretata. La vera risposta è che un linguaggio non può essere più veloce / lento di un altro: il tedesco è più veloce del giapponese?

  • Molte altre affermazioni relative a questa distinzione non hanno nemmeno senso.

  • Molte lingue usano approcci più complicati. Ad esempio, C # è compilato in IL, che è quindi "tradotto in codice nativo o eseguito da una macchina virtuale" .

  • Alcune lingue sono "interpretate, ma non proprio" .

Ora per rispondere alla tua domanda:

Modifica del codice:

Poiché il codice è interpretato, è più facile cambiarlo al volo quando lo si esegue. Uno di questi esempi è eval() , usato in alcune lingue per iniettare dinamicamente il codice sorgente disponibile solo come stringa.

Detto questo, questo punto è sia incompleto che confuso. C # è un linguaggio compilato (a parte DLR), e ancora, ci sono modi per iniettare codice personalizzato durante l'esecuzione dell'app.

Ottimizzazione del codice:

Spesso i compilatori non si limitano semplicemente a convertire il codice scritto nella lingua di partenza nella lingua di destinazione. Fanno anche ottimizzazioni. Immagina di scrivere:

const int ratio = 14;

function getFactor()
{
    int factor = 2 * ratio;
    string debug = "The factor is " + factor;
    return factor;
}

function computeSomething(int expenses)
{
    int factor;
    float result = (float)(expenses + factor) / 2;
    return result;
}

Un compilatore può utilizzare alcuni trucchi di base per ottimizzare questo codice:

  1. In linea

    La costante può essere inline, così come la prima funzione. Questo dà:

    function computeSomething(int expenses)
    {
        int factor = 2 * 14;
        string debug = "The factor is " + factor;
        float result = (float)(expenses + factor) / 2;
        return result;
    }
    
  2. Rimuovi il codice inutilizzato

    Qui, ciò comporterà un miglioramento importante, poiché la concatenazione di stringhe è spesso un'operazione costosa.

    function computeSomething(int expenses)
    {
        int factor = 2 * 14;
        float result = (float)(expenses + factor) / 2;
        return result;
    }
    
  3. Ottimizza le operazioni matematiche

    La moltiplicazione flottante è spesso più economica della divisione.

    function computeSomething(int expenses)
    {
        int factor = 2 * 14;
        float result = (float)(expenses + factor) * 0.5;
        return result;
    }
    
  4. Rimuovi le variabili intermedie

    function computeSomething(int expenses)
    {
        return (float)(expenses + (2 * 14)) * 0.5;
    }
    

Nota che molti interpreti di oggi fanno anche queste ottimizzazioni, il che significa che quando usi un linguaggio interpretato, dovresti scrivere il tuo codice per gli umani, non per i computer, lasciando che l'interprete funzioni.

    
risposta data 13.12.2013 - 08:33
fonte
0

Dopo averci pensato un po ', sto cambiando leggermente la mia risposta.

Ogni lingua può essere compilata. Ma il problema è quanto guadagni da questa compilation. La compilazione ha due vantaggi: il guadagno di prestazioni attraverso l'ottimizzazione e la verifica anticipata degli errori attraverso il controllo delle firme dei tipi. Entrambe queste cose dipendono in gran parte dall'indirizzamento indiretto nel codice. L'indirizzamento è semplicemente un pezzo di codice, che puoi dire cosa accadrà solo quando il codice è in esecuzione. In entrambi i casi di ottimizzazione e controllo dei tipi, qualsiasi tipo di indiretto rende inutili qualsiasi tentativo. Il compilatore non può ottimizzare il codice se non sa cosa succederà dopo o quale struttura funzionerà e non può garantire se i tipi sono corretti se non sa quali parametri può accettare la funzione o cosa è contenuto in una variabile.

Ora, i linguaggi che sono solitamente compilati, come C ++, C # e forse Java, questi contengono piccole correzioni. Ad esempio, in C #, le indecisioni possono essere raggiunte solo tramite metodi e delegati virtuali. Tutto il resto è semplice da prevedere senza dover eseguire il codice. Se chiami Math.Sqrt , il compilatore sa che al 100% chiamerà solo questo metodo. Il vantaggio derivante dalla compilazione di quelle lingue è enorme.

Dall'altro lato, le lingue che vengono interpretate spesso hanno in genere tonnellate di riferimenti indiretti. Questo spesso deriva dal sistema di tipo dinamico. Ad esempio, in Python, l'attributo di lettura della classe può restituire assolutamente qualsiasi cosa in qualsiasi momento. Non si sa mai se l'attributo read sarà int, string o function pointer. Quindi, anche se li compili, il guadagno è piuttosto trascurabile. Il compilatore può forse pre-analizzare il codice e fare alcune ottimizzazioni minori, ma non può fare cose come la funzione di inlining, che di solito ha un impatto molto più grande sulle prestazioni.

Modifica, vecchio post:

Non so cosa tu non capisca esattamente, perché entrambi questi punti sono piuttosto auto-esplicativi.

Con i linguaggi compilati la struttura del codice e dei dati è statica durante la compilazione e non può essere modificata durante il runtime. Questo non deriva dall'uso del compilatore, ma è necessario essere in grado di compilare una lingua. La struttura statica fornisce quindi informazioni sul compilatore necessarie per prevedere come il codice verrà eseguito e ottimizzato. Lingue come C ++, C # o Java rientrano in questa categoria.

Con linguaggi interpretati, la struttura del codice e dei dati può cambiare. Di solito, quando la struttura non è statica, la compilazione diventa quasi impossibile, quindi è necessaria l'interpretazione. Tuttavia, poiché l'interprete non richiede la struttura statica, la lingua può contenere funzionalità e semantica che consente di modificare i dati e il codice in fase di runtime. Le lingue digitate dinamicamente come Python, Ruby o JavaScript rientrano in questa categoria.

    
risposta data 13.12.2013 - 08:37
fonte
0

tl; dr: C'era una volta, c'erano compilatori e c'erano interpreti, e ognuno poteva essere chiaramente identificato. Ora, la maggior parte dei compilatori e la maggior parte degli interpreti sono in mezzo.

  • Non tutti i compilatori compilano il codice nativo - ad es. Java
  • Molti interpreti sono in realtà compilatori dietro le quinte (Perl, Python ecc.) e quindi possono fare le stesse ottimizzazioni
  • Alterazione del codice viene eseguita, nella mia esperienza, più nel codice compilato che nel codice interpretato; entrambi al momento della compilazione (ad esempio #ifdef di C) e molto occasionalmente in codice binario (ottimizzazioni molto specifiche o per nascondere cosa sta facendo il codice). Il tempo di compilazione è molto, molto comune, il codice binario è molto, molto raro.
  • Il codice compilato di solito contiene una tabella dei simboli, quindi puoi scoprire quale riga del codice sorgente corrisponde a un pezzo di codice compilato. Facilita lo sviluppo di codice interattivo.

L'alterazione del codice in fase di esecuzione (che può, in realtà, essere eseguita sia da un interprete che da un codice compilato) è una strategia molto discutibile e soggetta a errori. L'ho visto fatto in codice compilato (assembly) più che codice interpretato, e tende ad essere un pezzo di codice molto piccolo, che è MOLTO critico di tempo, o per scopi DRM (dove l'autore del codice non vuole che tu essere in grado di capire cosa sta facendo). L'ho visto anche per ragioni molto, molto sbagliate.

Per quanto riguarda il codice "interpretato", la maggior parte dei linguaggi "interpretati" moderni esegue la compilazione "just-in-time" - cioè, quando si esegue il codice, "interprete" compila il codice (spesso in bytecode), quindi può (e fa) anche l'ottimizzazione del codice. In alcuni casi, il codice compilato viene persino memorizzato nella cache, per essere riutilizzato se il file di origine non viene modificato. Un esempio di questo è Python, che memorizza il bytecode compilato in file .pyc.

Non tutti i compilatori compilano il codice macchina nativo; java, ad esempio, è compilato in bytecode java, per essere eseguito dalla macchina virtuale java, che, in un certo senso, è un interprete. Per confondere ulteriormente, alcune macchine java virtuali sono essi stessi compilatori "just-in-time". Alcuni byte di codice di compilazione anche usati frequentemente, e interpretano il resto.

    
risposta data 09.03.2014 - 22:23
fonte
0

Il campione migliore per la tua domanda sarà SmallTalk system / language.

Il sistema ST classico utilizza una macchina virtuale, esegue bytecode. Quando si avvia il sistema viene caricato dall'immagine dell'istantanea, contenente tutto il codice bytecode, IDE, debugger, strumenti di visualizzazione, editor, browser Web, applicazioni personalizzate scritte come estensioni per base ST e tutto l'ambiente di sviluppo è scritto in ST e compilato in bytecode.

Quindi, in un debugger puoi aprire qualsiasi punto dell'intero sistema (o della tua applicazione), modificare il suo codice sorgente e ricompilare questo codice al volo, sostituendo il codice in runtime nell'applicazione in esecuzione . Quindi il sistema ti consente di vedere gli elementi in forma compilata, nel codice sorgente e trasformarli manualmente o in modo programmatico.

Ad esempio, in interprete è possibile ottenere il codice sorgente per qualsiasi funzione come AST, eseguire alcune trasformazioni (sostituire le operazioni generiche con la variante più ottimizzata per la piattaforma corrente, ad esempio sostituire tutto [+ L: ... R: .. .] di [add_uint16 L: ... R: ...]), e ricompilare la funzione risultante in runtime. Oppure costruisci un codice di calcolo per programma ed eseguilo come codice macchina nudo usando l'ottimizzazione del compilatore JIT incorporato nel tuo sistema di runtime dell'interprete.

Nel caso di un compilatore (indipendentemente dal codice o dal codice macchina), hai perso molte informazioni sul tuo programma (tipi di dati, modelli generici, ..) e non hai la possibilità di modificarlo in fase di runtime.

    
risposta data 30.10.2017 - 21:59
fonte
-3

Come esempio pratico accendi EXCEL e crea una macro.

Avvia il debugger e passa attraverso il codice.

È possibile modificare il contenuto di qualsiasi variabile. Esegui espressioni arbitarie. Cambia il codice sorgente (in misura limitata - non puoi cambiare codice all'interno di un loop o altra struttura di controllo se l'istruzione corrente è all'interno della struttura ecc. Ecc.)

    
risposta data 13.12.2013 - 09:40
fonte
-3

La dichiarazione è altamente arbitraria perché ci sono anche tecniche di iniezione di codice che alterano programmi compilati. Con i linguaggi interpretati è solo più facile da fare.

Un sacco di virussi funzionano in questo modo.

    
risposta data 13.12.2013 - 10:26
fonte

Leggi altre domande sui tag