User Tools

Site Tools


appunti3s:gestione_della_memoria

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
appunti3s:gestione_della_memoria [2019/07/28 09:41] profproappunti3s:gestione_della_memoria [2023/10/02 09:54] (current) profpro
Line 1: Line 1:
 +> Per ;;tornare all'**indice** ;; degli argomenti clicca qui [[appunti3s:linguaggio_c|linguaggio C++]]
  
 +====== Gestione della memoria ======
 +
 +===== Introduzione =====
 +
 +A seconda di come e dove vengono definite, si possono avere diversi tipi di variabili:
 +  * variabili globali: definite fuori da ogni funzione (anche dal main) o classe
 +  * variabili locali (o automatiche): definite dentro una funzione (o classe)
 +  * variabili statiche: per approfondimento vedere [[appunti3s:possibili_approfondimenti#static|static]]
 +  * variabili dinamiche: termine usato per definire l'allocazione di memoria durante l'esecuzione (run time). N.B: var. dinamiche **non** è il contrario di var. statiche
 +
 +===== Risorsa memoria =====
 +Un programma contiene istruzioni da eseguire. 
 +Un programma in esecuzione sul sistema operativo viene chiamato //processo//.
 +Il file eseguibile, prima dell'esecuzione, viene trasferito dalla memoria di __massa__ alla memoria __centrale__, dove viene chiamato //immagine// del processo.
 +Nella memoria centrale il file eseguibile è diviso in diverse sezioni (section): text section, data setion e stack section.
 +Il sistema operativo può gestire ogni sezione, dividendola ulteriormente in segmenti (segment) (anche non contigui tra loro).
 +Tutto ciò è necessario perché un sistema operativo multitasking, che esegue più processi contemporaneamente, deve poter offrire ad ogni processo le limitate risorse hardware (come la memoria centrale) garantendo anche la sicurezza. È possibile approfondire questi argomenti studiando i sistemi operativi.
 +
 +{{:appunti3s:stack.png?|}}
 +==== Segment ====
 +Un segmento rappresenta un'area di memoria contigua pari a 64 kB
 +
 +==== Text segment ====
 +Area che contiene le istruzioni eseguibili e le costanti. Durante l'esecuzione del programma l'area è di dimensione fissa e di sola lettura. Viene chiamato anche "code segment".
 +
 +==== Data segment ====
 +Area che contiene le variabili cosiddette //[[appunti3s:programmazione_multifile#campi_di_visibilita|globali]]// e quelle //[[appunti3s:possibili_approfondimenti#possibili_approfondimenti|statiche]]// che sono state inizializzate esplicitamente dal programmatore. Questa area è di dimensione costante nel tempo, ma a differenza del precedente __non__ è di sola lettura, poiché è possibile modificare i valori delle variabili in runtime. Le variabili in quest'area sono visibili a tutte le funzioni per tutta la durata del programma.
 +
 +====BBS segment ====
 +(Block Started by Symbol) area che contiene le variabili cosiddette //globali// e quelle //statiche// che, a differenza del precedente caso, __non__ sono state inizializzate dal programmatore e che quindi vengono inizializzate automaticametne a zero dal compilatore. Le altre caratteristiche sono identiche a quelle del Data Segment (vedere l'[[appunti3s:programmazione_imperativa#inizializzazione]] delle variabili...)
 +
 +==== Heap segment ====
 +{{ :appunti3s:heap1.png|}} Significa letteralmente //mucchio//. È detta anche //memoria dinamica// perché all'interno di questa area (di dimensione non fissa) lo spazio viene riservato e liberato //dinamicamente//, in runtime, cioè durante l'esecuzione. Questo avviene sotto il controllo del programmatore, a seconda delle richieste dell'utente e delle istruzioni presenti nel programma. Per questi compiti il programmatore usa l'operatore //new// e l'operatore //delete//
 +Nel linguaggio C++ questa memoria è riservata e liberata con una gestione manuale (operatore new e operatore delete).
 +Nel linguaggio JAva l'operatore delete non esiste.
 +
 +==== Stack ====
 +{{ :appunti3s:heap2.png|}} Significa letteralmente //pila//. Come in una pila, gli elementi che compongono lo stack vengono riservati in modo ordinato e liberati in ordine inverso. È detta anche //memoria automatica// perché la gestione è automatica, sotto il controllo del sistema operativo. Lo stack contiene le variabili cosiddette //locali// e i //parametri// (come: int i) dichiarati nell'ambito dell'esecuzione di una funzione. Le variabili presenti in questa area sono visibili solo alle rispettive funzioni e solo per l'intervallo di tempo necessario alla funzione. Ogni elemento viene liberato automaticamente al termine della funzione (punto indicato con la parentesi graffa chiusa). 
 +
 +==== Confronto delle caratteristiche ====
 +
 +Una variabile //locale// (nello stack) ha una vita pari a quella del periodo di esecuzione della funzione. Una variabile allocata dinamicamente (nell'heap) con l'operatore //new// può avere una vita anche più lunga, perché non viene distrutta fino a che non si usa l'operatore //delete//. Può durare di più ma anche di meno...
 +
 +Quando non interviene il programmatore:
 +  * Le variabili globali, statiche e gli oggetti sono inizializzati automaticamente a zero.
 +  * Rimangono non inizializzate le altre variabili, cioè:
 +    * quelle locali (allocate nello stack)
 +    * quelle dinamiche (allocate con //new// nell'heap) 
 +
 +====== Puntatori a... (qualcosa) ======
 +Prima di spiegare i puntatori è necessario ricordare la distinzione tra la cosiddetta memoria automatica e memoria dinamica.
 +
 +  * **Memoria automatica** Le variabili automatiche (cosiddette locali) sono gestite automaticamente nello //stack// e quando non servono più vengono automaticamente "distrutte".
 +  * **Memoria dinamica** Durante l'esecuzione del programma può essere necessario allocare (riservare) dinamicamente della memoria. Questo accade per le operazioni dove non si possono prevedere in anticipo le richieste di un utente. 
 +Per riservare una zona di memoria dinamicamente si usa l'operatore //new//.
 +==== memoria automatica ====
 +Esempio di variabile gestito automaticamente (facile da usare)
 +<code>float z; // riserva la quantità di memoria necessaria per un numero in virgola mobile</code>
 +
 +==== New ====
 +Se durante la compilazione non si conosce quante variabili saranno necessarie, la memoria si allocherà dinamicamente (invece che automaticamente) con //new//. L'operatore //new// fa parte della libreria standard del C++, ma è una delle poche eccezioni in cui non è necessario specificare il namespace std. 
 +
 +Esempio:
 +
 +<code>new float; // riserva la quantità di memoria necessaria per un numero in virgola mobile</code>
 +
 +Se la memoria centrale fosse esaurita si otterrebbe da //new// un puntatore null, tuttavia includendo uno specifico header si può ottenere un'[[appunti3s:eccezioni]] //bad_alloc exception//. Questo è il codice (in generale non è obbligatorio usarlo):
 +<code>#include <new> </code>
 +
 +
 +Cosa restituisce l'operatore new? Restituisce l'indirizzo iniziale della memoria che è appena stata riservata. 
 +
 +Dove memorizzare un indirizzo? Quale tipo di dato è utile per memorizzare un indirizzo di memoria? 
 +Anche se un indirizzo di memoria è un numero, non si usa il tipo //int//, ma un tipo di dato speciale chiamato //puntatore a...//. Nel precedente esempio si deve usare un "//puntatore a float//". Per  dichiarare tale puntatore basta aggiungere l'operatore asterisco * dopo il nome del tipo:
 +<code>
 +float* numero;       //definizione della variabile puntatore a...
 +numero = new float;  // inizializzazione della variabile puntatore a..
 +</code>
 +
 +{{:appunti3s:puntatore1.png?|}}
 +
 +Nel precedente esempio il programma ha riservato due aree di memoria:
 +  * "numero" come variabile //automatica//, per contenere un indirizzo
 +  * "new float" come variabile //dinamica//, per contenere un numero in virgola mobile
 +Le due operazioni possono essere eseguite anche in un'unica volta:
 +{{ :appunti3s:puntatore.png|}}
 +<code>float* numero = new float;</code>
 +La seconda variabile (nell'heap) sarebbe inutilizzabile senza la prima (nello stack), ma chi è che controlla la "distruzione" della prima? Poiché "numero" è una variabile automatica essa si libera automaticamente quando termina la funzione che la contiene, quindi l'area allocata dinamicamente potrebbe diventare inutilizzabile nel caso in cui questo accadesse. Nell'esempio visto, il programmatore deve usare //delete// prima che si verifichi questo evento "disastroso":
 +
 +<code>delete numero;</code>
 +
 +Riassumendo, questo è l'ordine in cui si devono fare le cose
 +  * inizia la funzione
 +    - definire un puntatore (nella mem. automatica)
 +    - riservare la memoria dinamica con //new// e inizializzare il puntatore
 +    - utilizzare a proprio piacere l'area dinamica...
 +    - liberare la memoria dinamica con //delete//
 +  * termina la funzione (si libera anche il puntatore)
 +Oppure, in altre parole, ricordarsi di liberare tutte le variabili dinamiche create per evitare problemi di gestione della memoria o falle (memory leak).
 +I puntatori quindi sono variabili usate prevalentemente per controllare la memoria dinamica (//heap//).
 +
 +====Un consiglio====
 +Nonostante quanto detto nelle precedenti righe, sarebbe meglio non creare/distruggere variabili dinamiche in una funzione. L'utilizzo dei puntatori è un comune causa di errori o bug nei programmi, soprattutto se questi sono utilizzati per un lungo tempo. La strategia consigliata è di usare l'operatore //new// solo nei costruttori degli oggetti e l'operatore //delete// solo nei distruttori. Tali puntatori non dovrebbero essere nemmeno "esposti", cioè dovrebbero essere di tipo //private//.
 +
 +I puntatori (e gli array) lavorano sulla memoria a basso livello, cioè senza quasi nessun controllo, e possono causare oscuri malfunzionamenti e bug! I vector vanno preferiti perché hanno un'efficienza ancora sufficientemente elevata e sono più facili da usare (ci sono più controlli).
 +
 +Creando un [[appunti3s:tipi di dato strutturato array#vector]] (o uno [[appunti3s:oggetti di tipo stream|fstream]]) come oggetto locale, la distruzione sarà eseguita automaticamente al termine della funzione e così si posso evitare i puntatori.
 +In ogni caso, non usare direttamente new e delete, ma creare oggetti con new nel costruttore e delete nel distruttore.
 +
 +===== Perché usare la memoria dinamica? =====
 +
 +La memoria dinamica, rispetto alla memoria automatica, può essere utile per contenere oggetti molto grandi o di dimensioni non note a priori, oppure per contenere oggetti che devono durare a lungo (più della durata degli oggetti locali (automatici) di una funzione).
 +===== Perché usare i puntatori? =====
 +I puntatori non vengono usati solo per accedere alla memoria heap.
 +A volte i puntatori permettono di accedere anche a zone della memoria (stack) altrimenti inaccessibili, come quando si vuole che una funzione conosca l'indirizzo di una variabile usata da un'altra per poterla modificare. Ma questo utilizzo non dovrebbe essere necessario e probabilmente si basa su un cattivo stile di programmazione.
 +A volte i puntatori permettono di puntare alle funzioni, consentendo di eseguire funzioni diverse per condizioni diverse che si possono verificare in tempo di esecuzione (running time), in altre parole i puntatori consentono anche il [[appunti3s:polimorfismo]], a cui sarà dedicato un capitolo a parte. 
 +
 +
 +===== Differenze tra puntatori e array =====
 +<code>int a1[5];</code>
 +In linguaggio C scrivere "a1" equivale a scrivere l'indirizzo del primo elemento dell'array "&a1[0]" , quindi, quando si passa un array ad una funzione, in realtà si passa sempre il valore del suo indirizzo (come un puntatore). Per gli array vedere anche il capitolo [[appunti3s:tipi_di_dato_strutturato_array]]. 
 +
 +La differenza tra una variabile puntatore (int* p) e un indirizzo (&a1[0]) è che la prima può cambiare valore e puntare qualsiasi area di memoria, mentre il secondo, essendo l'indirizzo del primo elemento di un array, non può essere cambiato.
 +===== Similitudini tra puntatori e array =====
 +<code>int *p = a1</code>
 +Se il puntatore punta all'area di memoria di un array, si può utilizzare una sintassi simile a quella di un array:
 +<code>p[0]=3; </code>
 +
 +Nelle dichiarazioni delle funzioni si può usare equivalentemente una qualsiasi delle seguenti due sintassi: rif. standard 13.1-3.3
 +<code>int fun(int p[]);
 +int fun(int *p);</code>
 +Il passaggio di un array tramite una funzione, avviene sempre tramite un puntatore, ma un array __non__ è un puntatore...
 +===== Puntatore appeso =====
 +differenza tra 
 +
 +<code>
 +return &variabile; // (dangling pointer) resituisce l'indirizzo di una 
 +                   // var che si autodistrugge(locale,automatica)</code>
 +<code>
 +return puntatore; // restituisce un indirizzo (la copia del contenuto di 
 +                  // un puntatore) che contiene l'indirizzo di (si spera) 
 +                  // un'area allocata con new, che sopravvive alla funzione.</code>
 +
 +===== Puntatore a void=====
 +È introdotto nel linguaggio C++ per necessità. 
 +Ovviamente non esistono oggetti //void//. Il tipo //void// viene usato solo per indicare "nessun valore restituito" da una funzione.
 +
 +Il puntatore //void*// è usato solo per poter puntare qualsiasi cosa, che il programma non sa cosa sia (mentre il programmatore deve sapere cosa e').
 +Il tipo //void*// serve solo per copiare un indirizzo di memoria a basso livello e non consente di lavorare sul contenuto della memoria.
 +
 +Questo significa che per poter lavorare sul contenuto bisogna effettuare un casting verso un altro tipo di puntatore. Mentre il casting tra altri tipi può essere implicito, per i puntatori è necessario usare il casting esplicito... 
 +Mentre esistono conversioni implicite tra float e int, non esiste tra float* e int*
 +
 +====== smart pointers ======
 +http://ootips.org/yonat/4dev/smart-pointers.html
 +
 +====== shared pointers ======
 +to do...
 +
 +> Per ;;tornare all'**indice** ;; degli argomenti clicca qui [[appunti3s:linguaggio_c]]