Sommario
< Home
Stampa

Visibilità delle variabili

Regole di visibilità

In C++ esistono delle regole di visibilità delle variabili e delle costanti. Queste regole servono a stabilire dove queste variabili (o costanti) sono visibili all’interno del programma.

Sono definiti in generale tre livelli di visibilità:

  • Globale: una variabile globale è visibile ovunque nel file sorgente dove viene definito. Per essere visibile deve essere definita al di fuori di qualsiasi funzione, compreso main.

Esempio

#include <iostream>
using namespace std;

int x;

void someFunction() {
 cout << x; // stampa x
}

int main()
{
  x = 3;
  someFunction();
  return 0;
}
  • Funzione: una variabile definita in una funzione è visibile ovunque nella funzione stessa, ma non nelle altre funzioni (anche quelle che richiama): sono compresi tra queste anche gli argomenti della funzione. Le variabili definite in una funzione sono memorizzate nello stack e cancellate all’uscita della funzione; questo significa anche che ad ogni nuova esecuzione della funzione quella variabile sarà ricreata da zero.
#include <iostream>
using namespace std;

void someFunction() {
  int x = 3;
}

int main()
{
  someFunction();
  cout << x; // da errore, x non è visibile in main
  return 0;
}
  • Blocco: una variabile o costante definita in un blocco, è visibile solo all’interno del blocco stesso (e dei sottoblocchi). Questa visibilità comprende un eventuale variabile definita in un ciclo for, il cui valore è modificabile fuori dal blocco nell’istruzione for, ma è visibile solo all’interno del blocco del ciclo. Eventuali altre variabili definite in un blocco di un ciclo non mantengono il valore nell’esecuzione di eventuali cicli successivi e vengono ricreate da zero.
#include <iostream>
using namespace std;

int main() {
  {
    int x = 3;
  }
  cout << x; // da errore, x non è visibile fuori dal blocco
  return 0;
}

Variabili statiche

Normalmente, quando viene creata una variabile lo spazio di memoria ad essa dedicato viene allocato durante l’esecuzione del programma stesso, o se dentro una funzione nella funzione stessa. Se la variabile viene creata o distrutta più volte, ad esempio in esecuzioni differenti della stessa funzione, il programma creerà da zero una nuova veriabile (in una posizione di memoria probabilmente diversa).

Ad esempio nel codice seguente la variabile x viene ricreata ad ogni ciclo e distrutta al termine del ciclo.

#include <iostream>
using namespace std;

int main()
{
  for (int i=0; i<5; i++) {
    int x = 1; // x viene ricreata e distrutta 10 volte 
    x = x + i;
    cout << x << " "; 
    // questo codice stamperà "1 2 3 4 5"
  }
  return 0;
}

Il discorso cambia se si utilizza il modificatore “static”. In questo caso la variabile una volta creata, continua ad esistere anche dopo che la funzione termina, e quindi la variabile mantiene il suo valore.

Questo significa che rimane memoria di elaborazioni effettuate in precedenza.

#include <iostream>
using namespace std;

int main()
{
  for (int i=0; i<5; i++) {
    static int x = 1; // x viene creata solo la prima volta
    x = x + i;
    cout << x << " "; 
  }
  return 0;
}

Vediamo in dettaglio la sequenza di valori che verrà stampata:

cicloxix+i
1101
2112
3224
4437
57411

In pratica una volta definita una variabile static, rimane sempre in memoria, e quindi anche se ridichiarata in realtà continua ad usare il valore dell’ultima esecuzione. Quindi x avrà valore 1 al primo ciclo, ma dal ciclo successivo si sommerà al valore precedente.

Una variabile static segue comunque le regole di visibilità indicate sopra, quindi la visibilità dipende da dove viene dichiarata e segue le regole descritte nel paragrafo precedente.

Questo cosa significa concretamente?

  • Che quando viene definita una variabile static, se definita globalmente quella variabile sarà allocata staticamente in una posizione fissa della memoria del programma all’avvio dello stesso e sarà quindi visibile e modificabile in tutto il programma.
  • Quando viene definita dentro una funzione, sarà allocata alla prima esecuzione della funzione, ma manterrà il suo valore ad ogni nuova esecuzione della funzione, mantiene quindi anche questa una posizione fissa in memoria, ma sarà raggiungibile solo nel contesto in cui è dichiarata.
#include <iostream>
using namespace std;

int counter() {
  static int count = 0;
  count++;
  return count;
}
int main() {
  for (int i=0; i<5; i++) {
    cout << counter() << " "; // stampa "1 2 3 4 5"
  }
  return 0;
}
  • Se viene definita come static in un blocco, mantiene il valore precedente ad ogni nuova esecuzione di quel blocco, come visto nell’esempio sopra.

Il vantaggio dell’utilizzo di variabili statiche è di mantenere memoria di esecuzioni precedenti, cioè di memorizzare l’esecuzione di blocchi o funzioni precedenti.

Grazie alle variabili statiche è possibile creare funzioni che restituiscono array. Come noto infatti una funzione che restituisce un array è formalmente sempre possibile ma siccome l’array è creato all’interno di una funzione, il suo puntatore punta ad una risorsa che sta nello stack e che quindi viene eliminata al termine della funzione.

int* creaArray() {
    int arr[5]; 
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    return arr; 
}

Questo codice se eseguito restituisce sempre un puntatore ad un’area di memoria inesistente.

Se invece lo definiamo come static, allora lo spazio dell’array viene allocato staticamente e quindi viene restituito un valore che continua ad esistere anche dopo la fine dell’esecuzione della funzione.

int* creaArray() {
    static int arr[5]; 
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    return arr; 
}