Salta al contenuto
Note per Studenti Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria)

Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria)

Esame
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 { ... } →\to 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).

scope_tipi.cppcpp
#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.14159

Annidamenti: 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:

durata.cppcpp
#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 = silly
  • local_auto ha scope e durata locali: nasce a ogni chiamata, riparte da 0 e stampa sempre Auto: 0.
  • local_static ha scope locale (visibile solo in f) ma durata statica: alla fine di f esce dallo scope ma resta in memoria (nel data o nel BSS, non nello stack), e alla chiamata successiva ricorda il valore: stampa static: 0, 1, 2.
  • Altri esempi di "oggetto che sopravvive al proprio scope": un oggetto creato con new in una funzione (il puntatore locale sparisce all'uscita dallo scope, il blocco nell'heap resta finché non c'è la delete, 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 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 salvo static; new →\to dinamica fino alla delete.
  • Variabile che esce dallo scope restando in memoria:
cpp
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 static locale vive nello stack; credere che la static si reinizializzi a ogni chiamata; usare un nome fuori dal suo blocco.

Esercizi su questo argomento

Teoria collegata