Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria)
In questa pagina 3
Domanda aperta 2 del terzo appello 2022-23 del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. Teoria: C++ - scope, namespace e librerielo scope è la regione di testo in cui un nome è valido, dal punto di dichiarazione alla fine dello scope; i tipi di scope sono globale (di file), di classe, locale (blocco o lista argomenti di una funzione), di statement (for) e di namespace; funzioni dentro funzioni sono illegali, le classi locali da evitare; i namespace raggruppano nomi senza creare un tipo e si accedono col nome qualificato namespace::membro oppure con using (la using directive va evitata negli header); un header file è un insieme di dichiarazioni che chi lo include può usare (<> per i file di sistema, "" per quelli del progetto), dev'essere protetto dalle include guard (#ifndef/#define/#endif) per non includere due volte le stesse definizioni; il linker unisce i file oggetto con le librerie, statiche (copiate in ogni eseguibile, self-contained) o dinamiche (una sola copia condivisa, .so su Linux, .dll su Windows, ricompilabili senza toccare gli eseguibili).C++ - scope, namespace e librerie →, C++ - chiamate a funzione e layout della memoriauna chiamata a funzione crea un record di attivazione (RA) con una copia dei parametri e le variabili locali, impilato nello stack (LIFO) e tolto all'uscita; la dimensione dipende dalle variabili locali e il tempo per inserirlo è costante; il processo ha cinque regioni di memoria: text (istruzioni, spesso read-only), initialized data (globali e statiche inizializzate), uninitialized data o BSS (globali e statiche non inizializzate, azzerate dal sistema), stack (RA) e heap (allocazione dinamica con new e delete); la durata (storage duration) di una variabile è diversa dallo scope: le globali e le static sono statiche (esistono per tutto il programma, inizializzate una volta), le locali sono automatiche (create all'entrata nel blocco, distrutte in ordine inverso all'uscita); le globali sono inizializzate prima del main ma l'ordine fra file diversi non è definito.C++ - chiamate a funzione e layout della memoria →. La parte sugli scope è già svolta in Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria).
Testo. Introdurre il concetto di scope e illustrare tutti i tipi di scope visti nel corso. Per ciascuno di essi, riportare un esempio di codice. Illustrare la differenza tra scope e durata della memorizzazione, riportando un esempio di variabile che esce dallo scope rimanendo memorizzata.
Lo scope e i suoi tipi
Scope. Regione del testo di un programma in cui un nome è valido: dal punto di dichiarazione alla fine dello scope.
| tipo | dove | esempio |
|---|---|---|
| globale (di file) | fuori da ogni altro scope | int global_counter = 100; |
| di classe | dentro una classe | class Contatore { ... int valore; ... }; |
| locale | dentro un blocco { } o nella lista degli argomenti di una funzione |
int somma(int a, int b) { int risultato = a + b; ... }, { int x_interno; } |
| di statement | nell'intestazione di un for |
for (int i = 0; i < 3; ++i) |
| di namespace | uno scope con nome | namespace Geometria { ... } Geometria::area_cerchio(1.0) |
Il programma seguente contiene un esempio per ogni tipo, con i punti in cui un nome non è più visibile (le righe commentate non compilerebbero).
#include <iostream>
int global_counter = 100; // scope GLOBALE (di file): visibile ovunque sotto
namespace Geometria { // scope di NAMESPACE: uno scope con nome
constexpr double pi = 3.14159;
double area_cerchio(double r) { return pi * r * r; }
}
class Contatore { // scope di CLASSE: il testo dentro la classe
public:
void incrementa() { ++valore; } // valore e' visibile qui, anche se dichiarato dopo
int leggi() const { return valore; }
private:
int valore = 0;
};
int somma(int a, int b) // a e b: scope LOCALE (lista degli argomenti)
{
int risultato = a + b; // scope locale: dal punto di dichiarazione alla }
return risultato;
}
int main()
{
int x = 5; // locale a main
{ // un blocco crea uno scope annidato
int x_interno = x * 2; // esiste solo qui dentro
std::cout << "blocco: x_interno = " << x_interno << '\n';
}
// std::cout << x_interno; // errore: x_interno non e' piu' in scope
for (int i = 0; i < 3; ++i) // scope di STATEMENT: i vive solo nel for
global_counter += i;
// std::cout << i; // errore: i non esiste fuori dal for
Contatore c;
c.incrementa();
c.incrementa();
std::cout << "somma = " << somma(2, 3) << '\n';
std::cout << "global_counter = " << global_counter << '\n';
std::cout << "contatore = " << c.leggi() << '\n';
std::cout << "area = " << Geometria::area_cerchio(1.0) << '\n'; // nome qualificato
return 0;
}blocco: x_interno = 10
somma = 5
global_counter = 103
contatore = 2
area = 3.14159Annidamenti: funzioni in classi e blocchi in blocchi sono comuni, classi in classi rare, classi in funzioni da evitare, funzioni in funzioni illegali; un nome interno può nascondere (shadowing) uno esterno, raggiungibile con :: se globale.
Scope e durata: due concetti diversi
| scope | durata di memorizzazione (storage duration, classe di memorizzazione) | |
|---|---|---|
| domanda | dove, nel testo del programma, il nome è accessibile? | per quanto tempo, a run-time, l'oggetto è in memoria? |
| dipende da | il punto di dichiarazione | dove e come è definita la variabile |
| tipi | globale, classe, locale, statement, namespace | statica (esiste per tutto il programma), automatica (dall'entrata all'uscita del blocco, nello stack), dinamica (da new a delete, nell'heap) |
Le globali sono sempre statiche; una locale è automatica, salvo se dichiarata static; la memoria dinamica dura finché non la si libera. Le variabili statiche sono inizializzate una sola volta, prima di main le globali, e conservano il valore.
Una variabile che esce dallo scope rimanendo memorizzata
La variabile locale static: lo scope è locale (fuori dalla funzione il nome non esiste), ma la durata è statica, quindi l'oggetto resta in memoria e mantiene il valore fra una chiamata e l'altra. Confrontata con una locale automatica:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
string program_name = "silly"; // globale: statica, esiste per tutto il programma
vector<string> v; // globale
int global_zero; // globale non inizializzata: parte da 0
void f()
{
int local_auto = 0; // automatica: nasce e muore a ogni chiamata
static int local_static = 0; // statica: inizializzata UNA volta, resta fra le chiamate
cout << "Auto: " << local_auto++ << ", static: " << local_static++ << '\n';
}
int main()
{
f();
f();
f();
cout << "global_zero = " << global_zero << ", program_name = " << program_name << '\n';
return 0;
}Auto: 0, static: 0
Auto: 0, static: 1
Auto: 0, static: 2
global_zero = 0, program_name = sillylocal_autoha scope e durata locali: nasce a ogni chiamata, riparte da 0 e stampa sempreAuto: 0.local_staticha scope locale (visibile solo inf) ma durata statica: alla fine difesce dallo scope ma resta in memoria (nel data o nel BSS, non nello stack), e alla chiamata successiva ricorda il valore: stampastatic: 0, 1, 2.- Altri esempi di "oggetto che sopravvive al proprio scope": un oggetto creato con
newin una funzione (il puntatore locale sparisce all'uscita dallo scope, il blocco nell'heap resta finché non c'è ladelete, oppure diventa un memory leak); una variabile globale, che ha scope di file ma è visibile in tutto il programma e vive fino alla fine.
Versione ripasso
- Scope = regione di testo in cui un nome è valido (dalla dichiarazione alla fine dello scope). Tipi: globale (
int global_counter = 100;), di classe (valoreinContatore), locale (blocco o argomenti:somma(int a, int b)), di statement (for (int i = ...)), di namespace (Geometria::area_cerchio). Annidamenti: funzioni in classi e blocchi ok, classi in funzioni da evitare, funzioni in funzioni illegali; shadowing e::. Rimando: C++ - scope, namespace e librerielo scope è la regione di testo in cui un nome è valido, dal punto di dichiarazione alla fine dello scope; i tipi di scope sono globale (di file), di classe, locale (blocco o lista argomenti di una funzione), di statement (for) e di namespace; funzioni dentro funzioni sono illegali, le classi locali da evitare; i namespace raggruppano nomi senza creare un tipo e si accedono col nome qualificato namespace::membro oppure con using (la using directive va evitata negli header); un header file è un insieme di dichiarazioni che chi lo include può usare (<> per i file di sistema, "" per quelli del progetto), dev'essere protetto dalle include guard (#ifndef/#define/#endif) per non includere due volte le stesse definizioni; il linker unisce i file oggetto con le librerie, statiche (copiate in ogni eseguibile, self-contained) o dinamiche (una sola copia condivisa, .so su Linux, .dll su Windows, ricompilabili senza toccare gli eseguibili).C++ - scope, namespace e librerie →.
| scope | durata | |
|---|---|---|
| domanda | dove si vede il nome (testo)? | per quanto l'oggetto è in memoria (run-time)? |
| valori | globale, classe, locale, statement, namespace | statica, automatica (stack), dinamica (heap) |
- Durata: globali sempre statiche (inizializzate prima di
main); locali automatiche salvostatic;newdinamica fino alladelete. - Variabile che esce dallo scope restando in memoria:
void f() { int local_auto = 0; static int local_static = 0;
cout << "Auto: " << local_auto++ << ", static: " << local_static++ << '\n'; }
// f(); f(); f(); stampa Auto: 0, static: 0 / Auto: 0, static: 1 / Auto: 0, static: 2 local_static: scope locale ma durata statica (nel data/BSS): esce dallo scope, resta in memoria e ricorda il valore. Altri casi: oggetto new il cui puntatore locale sparisce (leak se non liberato); variabile globale.
- Errori tipici: identificare scope e durata; dire che la
staticlocale vive nello stack; credere che lastaticsi reinizializzi a ogni chiamata; usare un nome fuori dal suo blocco.