Come vengono memorizzate le variabili in un compilatore o interprete di una lingua?

9

Supponiamo di impostare una variabile in Python.

five = 5

Boom. Quello che mi chiedo è, come viene conservato? Il compilatore o l'interprete lo inserisce in una variabile come questa?

varname = ["five"]
varval  = [5]

Se questo è il modo in cui è fatto, dove è memorizzato? Sembra che questo potrebbe continuare all'infinito.

    
posta baranskistad 03.08.2017 - 23:19
fonte

3 risposte

13

Interprete

Un interprete lavorerà sul modo in cui hai indovinato. In un modello semplice, manterrà un dizionario con i nomi delle variabili come chiavi del dizionario e i valori delle variabili come valore del dizionario. Se la lingua conosce il concetto di variabili che sono visibili solo in contesti specifici, l'interprete manterrà più dizionari per riflettere i diversi contesti. L'interprete stesso è in genere un programma compilato, quindi per la sua archiviazione, vedi sotto.

Compiler

(Questo dipende molto dal linguaggio e dal compilatore ed è estremamente semplificato, quindi è solo pensato per dare qualche idea.)

Diciamo che abbiamo una variabile globale int five = 5 . Una variabile globale esiste solo una volta nel programma, quindi il compilatore riserva un'area di memoria di 4 byte (dimensione int) in un'area dati. Può usare un indirizzo fisso, diciamo 1234. Nel file eseguibile, il compilatore inserisce le informazioni che i quattro byte che iniziano a 1234 sono necessari come memoria dati statici, devono essere riempiti con il numero 5 all'avvio del programma e facoltativamente (per supporto debugger) l'informazione che il 1234 posto è chiamato five e contiene un intero. Ovunque qualche altra riga di codice fa riferimento alla variabile denominata five , il compilatore ricorda che è stata posizionata su 1234 e inserisce un'istruzione di lettura o scrittura in memoria per l'indirizzo 1234.

Se int six = 6 è una variabile locale all'interno di una funzione, dovrebbe esistere una volta per ogni chiamata attualmente attiva di questa funzione (può esserci più a causa di ricorsione o multi-threading). Quindi, ogni chiamata di funzione impila abbastanza spazio nello stack per contenere le sue variabili (inclusi quattro byte per la nostra variabile six . Il compilatore decide dove posizionare la variabile six all'interno di questo frame dello stack, forse a 8 byte dall'inizio del frame e ricorda quella posizione relativa, quindi le istruzioni che il compilatore produce per la funzione sono:

  • fa avanzare il puntatore dello stack di abbastanza byte per tutte le variabili locali della funzione.

  • memorizza il numero 6 (valore iniziale di six ) nella posizione di momory 8 byte sopra il puntatore dello stack.

  • ovunque la funzione si riferisca a six , il compilatore inserisce un'istruzione di lettura o scrittura per la posizione di momory 8 byte sopra il puntatore dello stack.

  • una volta terminato con la funzione, riavvolgi il puntatore dello stack sul suo vecchio valore.

Ancora una volta, questo è solo un modello molto semplificato, che non copre tutti i tipi di variabili, ma forse aiuta a capire ...

    
risposta data 04.08.2017 - 20:46
fonte
10

Dipende dall'implementazione.

Ad esempio, un compilatore C potrebbe mantenere una tabella dei simboli durante la compilazione. Si tratta di una struttura di dati ricca che consente di eseguire push e popping degli ambiti, poiché ogni parentesi di apertura della dichiarazione composta { introduce potenzialmente un nuovo ambito per nuove variabili locali. Oltre a gestire gli ambiti che vanno e vengono, registra le variabili dichiarate, e per ciascuna include i nomi e i loro tipi.

Questa struttura dati della tabella dei simboli supporta anche la ricerca delle informazioni di una variabile per nome, ad es. dal suo identificatore, e il compilatore lo fa quando lega le informazioni della variabile dichiarata agli identificatori grezzi che vede nel parse, quindi questo è abbastanza presto durante la compilazione.

Ad un certo punto, il compilatore assegna posizioni alle variabili. Forse le assegnazioni di posizione sono registrate nella stessa struttura dati della tabella dei simboli. Il compilatore potrebbe eseguire l'assegnazione della posizione direttamente durante l'analisi, ma è probabile che sia in grado di eseguire un lavoro migliore se attende non solo dopo l'analisi, ma dopo l'ottimizzazione generale.

Ad un certo punto, per le variabili locali, il compilatore assegna una posizione di stack o un registro CPU (può essere più complesso in quanto la variabile può effettivamente avere più posizioni, come una posizione di stack per alcune parti del codice generato e un registro CPU per altre sezioni).

Infine, il compilatore genera codice effettivo: istruzioni macchina che fanno riferimento ai valori delle variabili direttamente dai loro registri CPU o posizione di stack assegnata, come necessario per eseguire il codice che viene compilato. Ogni riga di codice sorgente viene compilata con le proprie serie di istruzioni di codice macchina, pertanto le istruzioni generate codificano non solo le operazioni (aggiunta, sottrazione) ma anche le posizioni delle variabili a cui si fa riferimento.

Il codice oggetto finale che esce dal compilatore non ha più nomi e tipi di variabili; ci sono solo posizioni, posizioni dello stack o registri della CPU. Inoltre non esiste una tabella di posizioni, ma piuttosto queste posizioni vengono utilizzate da ciascuna istruzione macchina conoscendo la posizione in cui è memorizzato il valore della variabile. Nessuna ricerca di identificatori nel codice di runtime, ogni bit di codice generato conosce semplicemente l'operazione da eseguire e la / le posizione / i da utilizzare.

Quando il debugging è abilitato durante la compilazione, il compilatore emetterà un modulo della tabella dei simboli in modo che, ad esempio, i debugger conoscano i nomi delle variabili nelle varie posizioni dello stack.

Alcuni altri linguaggi hanno la necessità di cercare gli identificatori dinamicamente in fase di runtime, quindi possono anche fornire qualche forma di tabella dei simboli a supporto di tali esigenze.

Gli interpreti hanno una vasta gamma di opzioni. Potrebbero mantenere una struttura di dati simile a una tabella di simboli da utilizzare durante l'esecuzione (oltre all'uso durante l'analisi), sebbene invece di assegnare / tracciare una posizione di stack, è sufficiente memorizzare il valore per la variabile, associato alla voce della variabile nella tabella dei simboli struttura dati.

Una tabella dei simboli è forse memorizzata nell'heap piuttosto che nello stack (sebbene l'utilizzo dello stack per scope e variabili sia certamente possibile, e inoltre può simulare uno stack nell'heap per ottenere il vantaggio della cache di compattare la variabile valori vicini l'uno all'altro), quindi un interprete sta probabilmente usando la memoria heap per memorizzare i valori della variabile mentre un compilatore usa le posizioni dello stack. In generale, anche l'interprete non ha la libertà di utilizzare i registri della CPU come memoria per i valori della variabile poiché i registri della CPU sono altrimenti occupati a eseguire le righe di codice dell'interprete stesso ...

    
risposta data 04.08.2017 - 00:10
fonte
-3

Il modo migliore per capire in cosa viene compilato il codice è compilare il codice all'assemblaggio . Il codice assembly è il più vicino alle istruzioni del processore che vengono eseguite.

    
risposta data 03.08.2017 - 23:29
fonte

Leggi altre domande sui tag