Come fanno i computer a ricordare dove archiviano le cose?


32

Quando un computer memorizza una variabile, quando un programma deve ottenere il valore della variabile, come fa il computer a sapere dove cercare in memoria il valore di quella variabile?


17
Non lo fa; "il computer" è completamente ignaro. Dobbiamo codificare tutti gli indirizzi. (Il che sta semplificando un po ', ma non troppo.)
Raffaello

1
@Raphael: generalizziamo a "dobbiamo hardcodificare gli indirizzi di base".
phresnel,

Ogni volta che dichiari una variabile, il programma responsabile dell'esecuzione del codice include il nome della variabile con il suo indirizzo in una tabella hash (ovvero spazio dei nomi). Suggerirei di leggere il libro "Struttura e implementazione dei programmi per computer (SICP) per conoscere bene questi piccoli dettagli.
Abhirath Mahipal

Il tuo programma sorgente utilizza una variabile. Il compilatore o l'interprete decide come implementarlo: genera istruzioni per l'esecuzione del computer e deve assicurarsi che tali istruzioni recuperino i valori dai punti in cui sono state memorizzate le istruzioni precedenti.
PJTraill,

1
@AbhirathMahipal: una variabile non deve avere un indirizzo in fase di compilazione o anche in fase di esecuzione; "Namespace" è un concetto di linguaggio mentre una tabella (con hash o altro) è un dettaglio di implementazione; il nome necessita di un cenno persistente nel programma quando viene eseguito.
PJTraill,

Risposte:


31

Ti suggerirei di guardare nel meraviglioso mondo della costruzione di compilatori! La risposta è che è un po 'complicato.

Per provare a darti un'intuizione, ricorda che i nomi delle variabili sono puramente lì per il bene del programmatore. Alla fine, il computer trasformerà tutto in indirizzi.

Le variabili locali sono (generalmente) memorizzate nello stack: cioè fanno parte della struttura di dati che rappresenta una chiamata di funzione. Possiamo determinare l'elenco completo delle variabili che una funzione utilizzerà (forse) osservando quella funzione, in modo che il compilatore possa vedere quante variabili sono necessarie per questa funzione e quanto spazio occupa ciascuna variabile.

C'è un po 'di magia chiamata puntatore dello stack, che è un registro che memorizza sempre l'indirizzo di dove inizia lo stack corrente.

A ogni variabile viene assegnato un "offset di stack", che è il punto in cui è memorizzato nello stack. Quindi, quando il programma deve accedere a una variabile x, il compilatore sostituisce xcon STACK_POINTER + x_offset, per ottenere l'effettiva posizione fisica in cui è memorizzato.

Si noti che questo è il motivo per cui si ottiene un puntatore indietro quando si utilizza malloco newin C o C ++. Non è possibile determinare dove si trova esattamente nella memoria un valore allocato in heap, quindi è necessario mantenere un puntatore ad esso. Quel puntatore sarà nello stack, ma punterà all'heap.

I dettagli dell'aggiornamento degli stack per le chiamate di funzione e i ritorni sono complicati, quindi consiglierei The Dragon Book o The Tiger Book se sei interessato.


24

Quando un computer memorizza una variabile, quando un programma deve ottenere il valore della variabile, come fa il computer a sapere dove cercare in memoria il valore di quella variabile?

Lo dice il programma. I computer non hanno nativamente un concetto di "variabili" - è una cosa del linguaggio di alto livello!

Ecco un programma C:

int main(void)
{
    int a = 1;
    return a + 3;
}

ed ecco il codice assembly che compila in: (commenti che iniziano con ;)

main:
    ; {
    pushq   %rbp
    movq    %rsp, %rbp

    ; int a = 1
    movl    $1, -4(%rbp)

    ; return a + 3
    movl    -4(%rbp), %eax
    addl    $3, %eax

    ; }
    popq    %rbp
    ret

Per "int a = 1;" la CPU vede l'istruzione "memorizza il valore 1 all'indirizzo (valore del registro rbp, meno 4)". Sa dove memorizzare il valore 1 perché il programma lo dice.

Allo stesso modo, l'istruzione successiva dice "carica il valore all'indirizzo (valore del registro rbp, meno 4) nel registro eax". Il computer non ha bisogno di sapere cose come le variabili.


2
Per connetterlo alla risposta di jmite, %rspè il puntatore dello stack della CPU. %rbpè un registro che fa riferimento al bit dello stack utilizzato dalla funzione corrente. L'uso di due registri semplifica il debug.
Salterio

2

Quando il compilatore o l'interprete incontra la dichiarazione di una variabile, decide quale indirizzo utilizzerà per memorizzare quella variabile, quindi registra l'indirizzo in una tabella dei simboli. Quando si incontrano riferimenti successivi a quella variabile, viene sostituito l'indirizzo dalla tabella dei simboli.

L'indirizzo registrato nella tabella dei simboli può essere un offset da un registro (come il puntatore dello stack) ma si tratta di un dettaglio di implementazione.


0

I metodi esatti dipendono da ciò di cui stai parlando e da quanto in profondità vuoi andare. Ad esempio, la memorizzazione di file su un disco rigido è diversa dalla memorizzazione di qualcosa in memoria o dalla memorizzazione di qualcosa in un database. Sebbene i concetti siano simili. E come lo fai a livello di programmazione è una spiegazione diversa da come un computer lo fa a livello di I / O.

La maggior parte dei sistemi utilizza una sorta di meccanismo di directory / indice / registro per consentire al computer di trovare e accedere ai dati. Questo indice / directory conterrà una o più chiavi e l'indirizzo in cui si trovano effettivamente i dati (che si tratti di disco rigido, RAM, database, ecc.).

Esempio di programma per computer

Un programma per computer può accedere alla memoria in vari modi. In genere il sistema operativo fornisce al programma uno spazio di indirizzi e il programma può fare quello che vuole con quello spazio di indirizzi. Può scrivere direttamente su qualsiasi indirizzo nel suo spazio di memoria e può tenere traccia di quello che vuole. Questo a volte può variare in base al linguaggio di programmazione e al sistema operativo, o anche in base alle tecniche preferite da un programmatore.

Come menzionato in alcune delle altre risposte, l'esatta codifica o programmazione utilizzata differisce, ma in genere dietro le quinte utilizza qualcosa come uno stack. Ha un registro che memorizza la posizione di memoria in cui inizia lo stack corrente e quindi un metodo per sapere dove si trova una funzione o una variabile in quello stack.

In molti linguaggi di programmazione di livello superiore, si occupa di tutto ciò per te. Tutto quello che devi fare è dichiarare una variabile e memorizzare qualcosa in quella variabile, e crea le pile e le matrici necessarie dietro le quinte per te.

Ma considerando quanto sia versatile la programmazione, in realtà non esiste una sola risposta, dal momento che un programmatore può scegliere di scrivere direttamente su qualsiasi indirizzo nel suo spazio assegnato in qualsiasi momento (supponendo che stia utilizzando un linguaggio di programmazione che lo consenta). Quindi potrebbe memorizzare la sua posizione in un array, o anche semplicemente codificarlo nel programma (cioè la variabile "alfa" è sempre memorizzata all'inizio dello stack o sempre nei primi 32 bit della memoria allocata).

Sommario

Quindi, in sostanza, ci deve essere un meccanismo dietro le quinte che dice al computer dove sono memorizzati i dati. Uno dei modi più popolari è una sorta di indice / directory che contiene le chiavi e l'indirizzo di memoria. Questo è implementato in tutti i modi e di solito è incapsulato dall'utente (e talvolta anche incapsulato dal programmatore).

Riferimento: come fanno i computer a ricordare dove archiviano le cose?


0

Lo sa a causa di modelli e formati.

Il programma / funzione / computer in realtà non sa dove si trova qualcosa. Si aspetta solo che qualcosa si trovi in ​​un determinato posto. Facciamo un esempio.

class simpleClass{
    public:
        int varA=58;
        int varB=73;
        simpleClass* nextObject=NULL;
};

La nostra nuova classe "simpleClass" contiene 3 variabili importanti: due numeri interi che possono contenere alcuni dati quando ne abbiamo bisogno e un puntatore a un altro "oggetto simpleClass". Supponiamo che siamo su una macchina a 32 bit per motivi di semplicità. 'gcc' o un altro compilatore 'C' farebbe un modello con cui lavorare per allocare alcuni dati.

Tipi semplici

In primo luogo, quando si utilizza una parola chiave per un tipo semplice come "int", il compilatore prende nota nella sezione ".data" o ".bss" del file eseguibile in modo tale che quando viene eseguito dal sistema operativo, i dati sono disponibile per il programma. La parola chiave "int" assegnerebbe 4 byte (32 bit), mentre un "long int" assegnerebbe 8 byte (64 bit).

A volte, in modo cella per cella, una variabile può venire subito dopo l'istruzione che dovrebbe caricarla in memoria, quindi sembrerebbe così in pseudo-assemblaggio:

...
clear register EAX
clear register EBX
load the immediate (next) value into EAX
5
copy the value in register EAX to register EBX
...

Ciò terminerebbe con il valore "5" in EAX e in EBX.

Durante l'esecuzione del programma, ogni istruzione viene eseguita ad eccezione del '5' poiché il carico immediato lo fa riferimento e fa saltare la CPU su di esso.

Il rovescio della medaglia di questo metodo è che è veramente pratico solo per le costanti, dal momento che non sarebbe pratico mantenere array / buffer / stringhe nel mezzo del codice. Pertanto, in genere, la maggior parte delle variabili viene mantenuta nelle intestazioni del programma.

Se fosse necessario accedere a una di queste variabili dinamiche, si potrebbe trattare il valore immediato come se fosse un puntatore:

...
clear register EAX
clear register EBX
load the immediate value into EAX
0x0AF2CE66 (Let's say this is the address of a cell containing '5')
load the value pointed to by EAX into EBX
...

Ciò terminerebbe con il valore '0x0AF2CE66' nel registro EAX e il valore di '5' nel registro EBX. Si possono anche aggiungere valori nei registri insieme, quindi saremo in grado di trovare elementi di una matrice o stringa usando questo metodo.

Un altro punto importante è che si è in grado di memorizzare valori quando si utilizzano gli indirizzi in modo simile, in modo da poter fare riferimento ai valori in quelle celle in un secondo momento.

Tipi complessi

Se realizziamo due oggetti di questa classe:

simpleClass newObjA;
simpleClass newObjB;

quindi possiamo assegnare un puntatore al secondo oggetto al campo disponibile per esso nel primo oggetto:

newObjA.nextObject=&newObjB;

Ora il programma può aspettarsi di trovare l'indirizzo del secondo oggetto nel campo del puntatore del primo oggetto. In memoria, questo sarebbe simile a:

newObjA:    58
            73
            &newObjB
            ...
newObjB:    58
            73
            NULL

Un fatto molto importante da notare qui è che 'newObjA' e 'newObjB' non hanno nomi quando vengono compilati. Sono solo luoghi in cui ci aspettiamo che siano presenti alcuni dati. Quindi, se aggiungiamo 2 celle a & newObjA, troviamo la cella che funge da 'nextObject'. Pertanto, se conosciamo l'indirizzo di 'newObjA' e dove la cella 'nextObject' è relativa ad essa, allora possiamo conoscere l'indirizzo di 'newObjB':

...
load the immediate value into EAX
&newObjA
add the immediate value to EAX
2
load the value in EAX into EBX

Ciò terminerebbe con "2 + & newObjA" in "EAX" e "& newObjB" in "EBX".

Modelli / Formati

Quando il compilatore compila la definizione della classe, sta davvero compilando un modo per creare un formato, un modo per scrivere in un formato e un modo per leggere da un formato.

L'esempio sopra riportato è un modello per un elenco collegato singolarmente con due variabili 'int'. Questi tipi di costruzioni sono molto importanti per l'allocazione dinamica della memoria, insieme agli alberi binari e n-ary. Le applicazioni pratiche degli alberi n-ary sarebbero filesystem composti da directory che puntano a file, directory o altre istanze riconosciute dai driver / dal sistema operativo.

Per accedere a tutti gli elementi, pensa a un verme che si fa strada su e giù per la struttura. In questo modo, il programma / funzione / computer non sa nulla, esegue solo le istruzioni per spostare i dati.


Le parole 'template' e 'format' come usate qui non compaiono in nessun compilatore o libro di testo del compilatore che io abbia mai visto, e non sembra esserci alcun motivo per usare entrambe le parole per la stessa cosa inesistente. Le variabili hanno indirizzi e / o offset, questo è tutto ciò che devi sapere.
user207421

Sto usando le parole poiché sono astrazioni per la disposizione dei dati, proprio come numeri, file, array e variabili sono astrazioni.
Mr. Minty Fresh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.