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, register, static. Określa ona sposób, w jaki zmiennej ma być przydzielana pamięć.
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.
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:
1. #include <iostream> 2. using namespace std; 3. 4. int stat = 10; 5. 6. void fun() { 7. static int stat; 8. cout << "stat lokalne " << stat++ << endl; 9. cout << "stat globalne " << ::stat << endl; 10. { 11. static int stat; 12. cout << "stat w bloku " << 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 tego programuwidzimy, że wszystkie trzy zmienne stat są niezależnymi, trwałymi zmiennymi. W szczególności, ostatnia z nich 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.
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: 5Lokalne zmienne licznik zliczają liczbę wywołań funkcji, w której są zadeklarowane, a globalna zmienna licznik zlicza liczbę wywołań obu funkcji.
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 .
Zmienne rejestrowe deklarowane są ze specyfikatorem register, np.:
register double x;Taka deklaracja stanowi wskazówkę dla kompilatora, że zmienna ta będzie na tyle często używana, że może być opłacalne, ze względu na efektywność programu, zarezerwowanie dla niej rejestru procesora (aby oszczędzić na czasie potrzebnym normalnie na przesyłanie wartości do i z pamięci lub cache'u). Kompilator może, ale nie musi, uwzględniać tej sugestii. Jeśli ma ją jednak uwzględnić, to nie wolno odwoływać się do adresu tej zmiennej (np. poprzez użycie wskaźników), gdyż zmienna w rejestrze w ogóle nie ma normalnego, dostępnego z poziomu języka adresu (adresy odnoszą się tylko do pamięci).
Specyfikatora register używa się bardzo rzadko. Współczesne kompilatory mają tak rozbudowane i doskonale zaprojektowane optymalizatory, że w normalnych warunkach możemy oczekiwać, że same będą lepiej wiedzieć, które zmienne opłaca się umieścić w rejestrach. Nasze podpowiedzi zwykle i tak niczego nie zmienią.
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łow 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 (najczęściej dając jakiś komunikat ostrzegawczy kompilatora).
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ć. 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
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. }
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 = 22po zlinkowaniu programu zarówno w funkcji main, jak i funkcji func widoczne są te same zmienne x1 i x2. Sama funkcja func nie musiała być deklarowana w pierwszym pliku jako extern (choć mogła być), gdyż funkcje domyślnie są eksportowane.
T.R. Werner, 21 lutego 2016; 20:17