[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 735.   Organizzazione della memoria

Nello studio del linguaggio C è importante avere un'idea di come venga gestita la memoria di un elaboratore, molto vicina a ciò che si percepirebbe usando direttamente il linguaggio della CPU.

735.1   Pila per salvare i dati

Quando si scrive con un linguaggio di programmazione molto vicino a quello effettivo del microprocessore, si ha normalmente a disposizione una pila di elementi omogenei (stack), usata per accumulare temporaneamente delle informazioni, da espellere poi in senso inverso. Questa pila è gestita attraverso un vettore, dove l'ultimo elemento (quello superiore) è individuato attraverso un indice noto come stack pointer e tutti gli elementi della pila sono comunque accessibili, in lettura e in sovrascrittura, se si conosce la loro posizione relativa.

Figura 735.1. Esempio di una pila che può contenere al massimo nove elementi, rappresentata nel modo tradizionale, oppure distesa, come si fa per i vettori. Gli elementi che si trovano oltre l'indice (lo stack pointer) non sono più disponibili, mentre gli altri possono essere letti e modificati senza doverli estrarre dalla pila.

pila

Per accumulare un dato nella pila (push) si incrementa di una unità l'indice e lo si inserisce in quel nuovo elemento. Per estrarre l'ultimo elemento dalla pila (pop) si legge il contenuto di quello corrispondente all'indice e si decrementa l'indice di una unità.

735.2   Chiamate di funzioni

I linguaggi di programmazione più vicini alla realtà fisica della memoria di un elaboratore, possono disporre solo di variabili globali ed eventualmente di una pila, realizzata attraverso un vettore, come descritto nella sezione precedente. In questa situazione, la chiamata di una funzione può avvenire solo passando i parametri in uno spazio di memoria condiviso da tutto il programma. Ma per poter generalizzare le funzioni e per consentire la ricorsione, ovvero per rendere le funzioni rientranti, il passaggio dei parametri deve avvenire attraverso la pila in questione.

Per mostrare un esempio che consenta di comprendere il meccanismo, si può osservare l'esempio seguente, schematizzato attraverso una pseudocodifica: la funzione SOMMA prevede l'uso di due parametri (ovvero due argomenti nella chiamata) e di una variabile «locale». Per chiamare la funzione, occorre mettere i valori dei parametri nella pila; successivamente, si dichiara la stessa variabile locale nella pila. Si consideri che il programma inizia e finisce nella funzione MAIN, all'interno della quale si fa la chiamata della funzione SOMMA:

SOMMA (X, Y)
    LOCAL Z INTEGER
    Z := X + Y
    RETURN Z
END SOMMA

MAIN ()
    LOCAL A INTEGER
    LOCAL B INTEGER
    LOCAL C INTEGER
    A := 3
    B := 4
    C := SOMMA (A, B)
END MAIN

Nel disegno successivo, si schematizza ciò che accade nella pila (nel vettore che rappresenta la pila dei dati), dove si vede che inizialmente c'è una situazione indefinita, con l'indice «sp» (stack pointer) in una certa posizione. Quando viene eseguita la chiamata della funzione, automaticamente si incrementa la pila inserendo gli argomenti della chiamata (qui si mettono in ordine inverso, come si fa nel linguaggio C), mettendo in cima anche altre informazioni che nello schema non vengono chiarite (nel disegno appare un elemento con dei puntini di sospensione).

Figura 735.3. Situazione della pila nelle varie fasi della chiamata della funzione SOMMA.

pila

La variabile locale «Z» viene allocata in cima alla pila, incrementando ulteriormente l'indice «sp». Al termine, la funzione trasmette in qualche modo il proprio risultato (tale modalità non viene chiarita qui e dipende dalle convenzioni di chiamata) e la pila viene riportata alla sua condizione iniziale.

Dal momento che l'esempio di programma contiene dei valori particolari, il disegno di ciò che succede alla pila dei dati può essere reso più preciso, mettendo ciò che contengono effettivamente le varie celle della pila.

Figura 735.4. Situazione della pila nelle varie fasi della chiamata della funzione SOMMA, osservando i contenuti delle varie celle.

pila

735.3   Variabili e array

Con un linguaggio di programmazione molto vicino alla realtà fisica dell'elaboratore, la memoria centrale viene vista come un vettore di celle uniformi, corrispondenti normalmente a un byte. All'interno di tale vettore si distendono tutti i dati gestiti, compresa la pila descritta nelle prime sezioni del capitolo. In questo modo, le variabili in memoria si raggiungono attraverso un indirizzo che individua il primo byte che le compone ed è compito del programma il sapere di quanti byte sono composte complessivamente.

Figura 735.5. Esempio di mappa di una memoria di soli 256 byte, dove sono evidenziate alcune variabili. Gli indirizzi dei byte della memoria vanno da 0016 a FF16.

memoria

Nel disegno in cui si ipotizza una memoria complessiva di 256 byte, sono state evidenziate alcune aree di memoria:

Indirizzo Dimensione Indirizzo Dimensione
5416 4 byte 5816 4 byte
5C16 2 byte 5E16 4 byte
6216 8 byte 6A16 4 byte
6E16 1 byte 6F16 8 byte

Con una gestione di questo tipo della memoria, la rappresentazione degli array richiede un po' di impegno da parte del programmatore. Nella figura successiva si rappresenta una matrice a tre dimensioni; per ora si ignorino i codici numerici associati alle celle visibili.

Figura 735.7. La matrice a tre dimensioni che si vuole rappresentare, secondo un modello spaziale. I numeri che appaiono servono a trovare successivamente l'abbinamento con le celle di memoria utilizzate.

matrice

Dal momento che la rappresentazione tridimensionale rischia di creare confusione, quando si devono rappresentare matrici che hanno più di due dimensioni, è più conveniente pensare a strutture ad albero. Nella figura successiva viene tradotta in forma di albero la matrice rappresentata precedentemente.

Figura 735.8. La matrice a tre dimensioni che si vuole rappresentare, tradotta in uno schema gerarchico (ad albero).

matrice

Si suppone di rappresentare la matrice in questione nella memoria dell'elaboratore, dove ogni elemento terminale contiene due byte. Supponendo di allocare l'array a partire dall'indirizzo 7716 nella mappa di memoria già descritta, si potrebbe ottenere quanto si vede nella figura successiva. A questo punto, si può vedere la corrispondenza tra gli indirizzi dei vari componenti dell'array e le figure già mostrate.

Figura 735.9. Esempio di mappa di memoria in cui si distende un array che rappresenta una matrice a tre dimensioni con tre elementi contenenti ognuno due elementi che a loro volta contengono quattro elementi da due byte.

memoria

Si pone quindi il problema di scandire gli elementi dell'array. Considerando che array ha dimensioni «3,2,4» e definendo che gli indici partano da zero, l'elemento [0,0,0] corrisponde alla coppia di byte che inizia all'indirizzo 7716, mentre l'elemento [2,1,3] corrisponde all'indirizzo A516. Per calcolare l'indirizzo corrispondente a un certo elemento occorre usare la formula seguente, dove: le variabili I, J, K rappresentano la dimensioni dei componenti; le variabili i, j, k rappresentano l'indice dell'elemento cercato; la variabile A rappresenta l'indirizzo iniziale dell'array; la variabile s rappresenta la dimensione in byte degli elementi terminali dell'array.

A + (i*J*K*s + j*K*s + k*s)

A + (i*J*K*s + j*K*s + k*s)

Si vuole calcolare la posizione dell'elemento 2,0,1. Per facilitare i conti a livello umano, si converte l'indirizzo iniziale dell'array in base dieci: 7716 = 11910:

A + (i*J*K*s + j*K*s + k*s)

Il valore 15310 si traduce in base sedici in 9916, che corrisponde effettivamente all'elemento cercato: terzo elemento principale, all'interno del quale si cerca il primo elemento, all'interno del quale si cerca il secondo elemento finale.

735.3.1   Esercizio

Una certa variabile occupa quattro unità di memoria, a partire dall'indirizzo 2F16. Qual è l'indirizzo dell'ultima unità di memoria occupata dalla variabile?

Indirizzo iniziale Indirizzo dell'ultima unità di memoria della variabile
2F16  
 
 
 

735.3.2   Esercizio

In memoria viene rappresentato un array di sette elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, qual è l'indirizzo dell'ultima cella di memoria usata da questo array?

Indirizzo iniziale Indirizzo dell'ultima unità di memoria dell'array
1716  
 
 
 

735.3.3   Esercizio

In memoria viene rappresentato un array di elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, qual è l'indirizzo iniziale del secondo elemento dell'array?

Indirizzo iniziale Indirizzo del secondo elemento dell'array
1716  
 
 
 

735.3.4   Esercizio

In memoria viene rappresentato un array di n elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, a quale elemento punta l'indirizzo 2B16?

Indirizzo iniziale Indirizzo dato Elemento dell'array
1716 2B16  
 
 
 

735.3.5   Esercizio

In memoria viene rappresentato un array di n elementi da quattro unità di memoria ciascuno. Se l'indirizzo iniziale di questo array è 1716, l'indirizzo 2216 potrebbe puntare all'inizio di un certo elemento di questo?

735.4   Ordine dei byte

Come già descritto in questo capitolo, normalmente l'accesso alla memoria avviene conoscendo l'indirizzo iniziale dell'informazione cercata, sapendo poi per quanti byte questa si estende. Il microprocessore, a seconda delle proprie caratteristiche e delle istruzioni ricevute, legge e scrive la memoria a gruppetti di byte, più o meno numerosi. Ma l'ordine dei byte che il microprocessore utilizza potrebbe essere diverso da quello che si immagina di solito.

Figura 735.17. Confronto tra big endian e little endian.

little endian, big endian

A questo proposito, per quanto riguarda la rappresentazione dei dati nella memoria, si distingue tra big endian, corrispondente a una rappresentazione «normale», dove il primo byte è quello più significativo (big), e little endian, dove la sequenza dei byte è invertita (ma i bit di ogni byte rimangono nella stessa sequenza standard) e il primo byte è quello meno significativo (little). La cosa importante da chiarire è che l'effetto dell'inversione nella sequenza porta a risultati differenti, a seconda della quantità di byte che compongono l'insieme letto o scritto simultaneamente dal microprocessore, come si vede nella figura.

735.4.1   Esercizio

In memoria viene rappresentata una variabile di 2 byte di lunghezza, a partire dall'indirizzo 2116, contenente il valore 11111100110000002. Se la CPU accede alla memoria secondo la modalità big endian, che valore si legge all'indirizzo 2116 se si pretende di trovare una variabile da un solo byte?

bit

Cosa si legge, invece, se la CPU accede alla memoria secondo la modalità little endian (invertita)?

bit

735.5   Stringhe, array e puntatori

Le stringhe sono rappresentate in memoria come array di caratteri, dove il carattere può impiegare un byte o dimensioni multiple (nel caso di UTF-8, un carattere viene rappresentato utilizzando da uno a quattro byte, a seconda del punto di codifica raggiunto). Il riferimento a una stringa viene fatto come avviene per gli array in generale, attraverso un puntatore all'indirizzo della prima cella di memoria che lo contiene; tuttavia, per non dovere annotare la dimensione di tale array, di norma si conviene che la fine della stringa sia delimitata da un byte a zero, come si vede nell'esempio della figura.

Figura 735.20. Stringa conclusa da un byte a zero (zero terminated string), a cui viene fatto riferimento per mezzo di una variabile che contiene il suo indirizzo iniziale. La stringa contiene il testo Ciao amore., secondo la codifica ASCII.

stringa

Nella figura si vede che la variabile scalare collocata all'indirizzo 5316 contiene un valore da intendere come indirizzo, con il quale si fa riferimento al primo byte dell'array che rappresenta la stringa (in posizione 7816). La variabile collocata in 5316 assume così il ruolo di variabile puntatore e, secondo il modello ridotto di memoria della figura, è sufficiente un solo byte per rappresentare un tale puntatore, dal momento che servono soltanto valori da 0016 a FF16.

735.5.1   Esercizio

In memoria viene rappresentata la stringa «Ciao a tutti». Sapendo che ogni carattere utilizza un solo byte e che la stringa è terminata regolarmente con il codice nullo di terminazione (0016), quanti byte occupa la stringa in memoria?

735.5.2   Esercizio

In memoria viene rappresentata la stringa «Ciao a tutti» (come nell'esercizio precedente). Sapendo che la stringa inizia all'indirizzo 3F16, a quale indirizzo si trova la lettera «u» di «tutti»?

735.5.3   Esercizio

Se la memoria dell'elaboratore consente di raggiungere indirizzi da 000016 a FFFF16, quanto deve essere grande una variabile scalare che si utilizza come puntatore? Si indichi la quantità di cifre binarie.

735.6   Utilizzo della memoria

La memoria dell'elaboratore viene utilizzata sia per contenere i dati, sia per il codice del programma che li utilizza. Ogni programma ha un proprio spazio in memoria, che può essere reale o virtuale; all'interno di questo spazio, la disposizione delle varie componenti potrebbe essere differente. Nei sistemi che si rifanno al modello di Unix, nella parte più «bassa» della memoria risiede il codice che viene eseguito; subito dopo vengono le variabili globali del programma, mentre dalla parte più «alta» inizia la pila dei dati che cresce verso indirizzi inferiori. Si possono comunque immaginare combinazioni differenti di tale organizzazione, pur rispettando il vincolo di avere tre zone ben distinte per il loro contesto (codice, dati, pila); tuttavia, ci sono situazioni in cui i dati si trovano mescolati al codice, per qualche ragione.

Figura 735.21. Esempio di disposizione delle componenti di un programma in esecuzione in memoria, secondo il modello Unix.

memoria

735.7   Riferimenti


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome organizzazione_della_memoria_1.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory