Podrozdziały


7.3 Klasy pamięci

Klasa pamięci zmiennej (ang. storage class specifier) określana jest w deklaracji za pomocą specyfikatora, który jest jednym ze słów kluczowych extern lub static (trzecie, register, nie jest już używane, choć pozostaje słowem kluczowym). Określa on sposób, w jaki zmiennej ma być przydzielana pamięć.


7.3.1 Zmienne statyczne

Zmienne statyczne deklarowane są ze specyfikatorem static. Mogą to być zarówno zmienne globalne, jak i lokalne.

Zmienna statyczna jest tworzona tylko raz i inicjowana po utworzeniu zerem odpowiedniego typu. Jeśli jest to zmienna lokalna, to tworzona jest, gdy przepływ sterowania przechodzi po raz pierwszy przez jej deklarację. Jej czas życia to okres od utworzenia do końca programu. Podobnie zmienna statyczna globalna tworzona jest raz i trwa do końca programu. Jaka jest zatem różnica między zmienną globalną niestatyczną a globalną statyczną? Przecież zmienne globalne (zdefiniowane bez specyfikatora static) też tworzone są raz i trwają do końca programu. Różnica jest taka, że jeśli zmienne globalne zadeklarujemy z atrybutem static, to będą widoczne w danym module (jednostce translacji) i tylko w nim: nie można się do nich odwołać z innego modułu nawet poprzez użycie słowa kluczowego extern (patrz dalej). Mówimy, że takie zmienne globalne nie są eksportowane. Obecnie nie zaleca się stosowania statycznych zmiennych globalnych; ten sam efekt można uzyskać poprzez użycie przestrzeni nazw — będziemy o tym mówić w rozdziale o przestrzeniach nazw .

Zmienna statyczna lokalna, mimo że widoczna jest tylko wewnątrz funkcji lub bloku, w którym została zadeklarowana, nie jest usuwana po wyjściu sterowania z tej funkcji (bloku). Po powrocie sterowania do tej samej funkcji wartość tej zmiennej zostanie zachowana — jest to bowiem wciąż fizycznie ta sama zmienna, w tym samym wciąż miejscu w pamięci komputera.

Zauważmy, co ilustruje poniższy program, że zmienne statyczne, tak lokalne jak i globalne, też można wewnątrz funkcji przesłaniać. Zarówno przesłaniane, jak i przesłaniające zmienne statyczne zachowują wtedy swą tożsamość pomiędzy wywołaniami funkcji:


P34: stat.cpp     Zmienne statyczne

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  int stat = 10;
      5.  
      6.  void fun() {
      7.      static int stat;
      8.      cout << "local  stat " << stat++ << endl;
      9.      cout << "global stat " << ::stat << endl;
     10.      {                                         
     11.          static int stat;
     12.          cout << "block  stat " << stat--
     13.                                 << "\n\n";
     14.      }
     15.  }
     16.  
     17.  int main() {
     18.      fun();
     19.      fun();
     20.      fun();
     21.  }

Z wyników tego programu

    stat lokalne  0
    stat globalne 10
    stat w bloku  0

    stat lokalne  1
    stat globalne 10
    stat w bloku  -1

    stat lokalne  2
    stat globalne 10
    stat w bloku  -2
widzimy, że wszystkie trzy zmienne stat są niezależnymi, trwałymi zmiennymi. W szczególności, ostatnia z nich, wewnątrz nieco sztucznego bloku rozpoczynającego sie w linii , również zachowuje swoją wartość pomiędzy wywołaniami funkcji, niezależnie od zmiennej stat zadeklarowanej w pierwszej linii funkcji fun.

Oczywiście, nic nie stoi na przeszkodzie, aby w różnych funkcjach używać lokalnych zmiennych statycznych o tej samej nazwie — będą one od siebie całkowicie niezależne. W poniższym przykładzie istnieją trzy statyczne zmienne licznik: dwie lokalne w dwóch różnych funkcjach i jedna globalna.


P35: static.cpp     Statyczne liczniki wywołań

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  int licznik;
      5.  
      6.  void fun1() {
      7.      static int licznik;
      8.      licznik++;   // lokalna
      9.      ::licznik++; // globalna
     10.      cout << "Wywolan   fun1: " << licznik << endl;
     11.  }
     12.  
     13.  void fun2() {
     14.      static int licznik;
     15.      licznik++;   // lokalna
     16.      ::licznik++; // globalna
     17.      cout << "Wywolan   fun2: " << licznik << endl;
     18.  }
     19.  
     20.  int main() {
     21.      fun1(); fun1(); fun2(); fun1(); fun2();
     22.      cout << "Wywolan fun1/2: " << licznik << endl;
     23.  }

Wynikiem programu jest

    Wywolan   fun1: 1
    Wywolan   fun1: 2
    Wywolan   fun2: 1
    Wywolan   fun1: 3
    Wywolan   fun2: 2
    Wywolan fun1/2: 5
Lokalne zmienne licznik zliczają liczbę wywołań funkcji, w której są zadeklarowane, a globalna zmienna licznik zlicza liczbę wywołań obu funkcji.


7.3.2 Zmienne zewnętrzne

Zmienne zewnętrzne deklarowane są ze specyfikatorem extern, np.:

       extern double x;
Deklaracja musi się znajdować poza funkcjami i klasami, a więc w zakresie globalnym. Stanowi ona informację dla kompilatora, że zmienna ta jest lub będzie zdefiniowana w innym pliku/module. Definicja z kolei może pojawić się tylko raz, w jednym z modułów składających się na program, jako definicja zmiennej globalnej bez specyfikatora extern. Zatem deklaracja ze specyfikatorem extern to przypadek, gdy deklaracja zmiennej nie jest związana z jej definicją: żadna zmienna nie jest tworzona, tzn. pamięć nie jest przydzielana.

Ta sama zmienna może być zadeklarowana (w identyczny sposób, tzn. z zachowaniem zgodności typu) wiele razy — również wielokrotnie w jednym pliku — ale zdefiniowana może być tylko raz. Zmiennej tylko deklarowanej (więc ze specyfikatorem extern) nie wolno inicjować: deklaracja nie wiąże się z przydziałem pamięci, więc nie byłoby gdzie wpisać wartości inicjującej. Większość kompilatorów potraktuje

       extern double x = 1.5;
tak jak po prostu
       double x = 1.5;
gdyż deklaracja połączona z inicjalizacją zmusza kompilator do zaalokowania pamięci, a więc do zdefiniowania zmiennej. Słowo extern zostanie wtedy zignorowane.

To, co powiedzieliśmy dotyczy zmiennych. Jeśli deklarujemy w module funkcję, której definicja podana jest w innym module, to słowa kluczowego extern nie trzeba używać (kiedyś było trzeba). Szerzej o funkcjach w rozdziale o funkcjach .

Rozważmy program składający się z dwóch plików: pierwszy z nich zawiera funkcję main


P36: exter1.cpp     Zmienne "extern". Plik 1

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  double x1 = 11;
      5.  extern double x2;
      6.  void func();
      7.  
      8.  int main()
      9.  {
     10.      cout << "main: x1 = " << x1 << endl;
     11.      cout << "main: x2 = " << x2 << endl;
     12.      func();
     13.  }

a drugi funkcję func


P37: exter2.cpp     Zmienne "extern". Plik 2

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  extern double x1;
      5.  double x2 = 22;
      6.  
      7.  void func()
      8.  {
      9.      cout << "func: x1 = " << x1 << endl;
     10.      cout << "func: x2 = " << x2 << endl;
     11.  }

Zmienna x1 jest definiowana jako zmienna globalna w pliku pierwszym, a w drugim tylko deklarowana. Odwrotnie zmienna x2 — ta jest definiowana w pliku drugim, a deklarowana w pliku pierwszym. Jak widzimy z wyniku

    cpp> g++ -o extern exter1.cpp exter2.cpp
    cpp> ./extern
    main: x1 = 11
    main: x2 = 22
    func: x1 = 11
    func: x2 = 22
po zlinkowaniu programu zarówno w funkcji main, jak i funkcji func widoczne są te same zmienne x1x2. Sama funkcja func nie musiała być deklarowana w pierwszym pliku jako extern (choć mogła być), gdyż funkcje domyślnie eksportowane.

T.R. Werner, 23 lutego 2019; 23:59