Podręcznik
Wersja podręcznika: 1.0
Data publikacji: 01.01.2022 r.
3. Definicje zmiennych i ich zasięg
3.3. Modyfikatory deklaracji zmiennych i stałych
Umieszczaniem zmiennych w pamięci oraz ich zachowaniem można w pewien ograniczony sposób sterować. Odpowiednie właściwości uzyskujemy przez modyfikatory podawane w momencie definicji zmiennej:
- register – oznacza, że zmienna ma być trzymana w rejestrze procesora, a nie w pamięci. Co prawda znów mamy tu do czynienia z wyrażeniem woli programisty – ostateczna decyzja zostanie podjęta przez kompilator, ale przynajmniej przekazujemy mu wskazówki. Przy czym wskazówka zostanie odrzucona, jeśli … choć raz spróbujemy uzyskać wskaźnik (adres) takiej zmiennej.
- const – oznacza, że obiekt jest stały – nie będzie można zmieniać jego wartości. Konsekwencją tego jest obowiązek zainicjowania go (podania wartości początkowej, której nie będzie można zmienić).
- volatile – oznacza, że nie ma się wyłączności do podanego obiektu (tzn. może być to rejestr sprzętowy komputera albo zmienna używana przez inny wątek). W praktyce takie zmienne kompilator pomija w procesie optymalizacji dostępu – każdy odczyt wartości zmiennej musi się wiązać z jej ponownym pobraniem z pamięci operacyjnej.
- static – w ogólności oznacza, że obiekt taki istnieje przez cały czas, niezależnie od zasięgu, który go używa (zabrania się w ten sposób kasowania zmiennych lokalnych). Jeśli static zastosujemy do zmiennej lokalnej w funkcji - to każde nowe wywołanie funkcji będzie miało dostęp do jej wartości z poprzednich wywołań tej funkcji. Dla zmiennych które i tak już istnieją cały czas (zmiennych globalnych) oznacza z kolei zniesienie zewnętrznego symbolu obiektu (tzn. poza bieżącą jednostką kompilacji, czyli plikiem, nic nie może z tego korzystać). Trochę niemiłe zachowanie – bo mamy rozszerzenie dostępu do zmiennych lokalnych, i zawężenie dla zmiennych globalnych. W przypadku funkcji static ma podobne znaczenie jak w przypadku stałych - przekształca funkcję w funkcję lokalną, bez możliwości korzystania z niej poza danym modułem / jednostką kompilacji.
- extern – o nim więcej za chwilę.
Modyfikatory static i extern wzajemnie się wykluczają, zwłaszcza, że oznaczają dwie całkiem przeciwne właściwości.
Przyjrzyjmy się dokładniej zmiennym statycznym. Taką zmienną można zainicjalizować, jednak jest to inicjalizacja podobna do inicjalizacji zmiennej globalnej - wykonuje się tylko raz. Jeśli tego nie zrobimy, przypisana jej będzie wartość zerowa. W poniższym przykładzie wykorzystaliśmy zmienną statyczną do zliczania i wyświetlania liczby wywołań funkcji:
#include <iostream>
using namespace std;
void mojaFunkcja(int a, int b) {
// zmienna statyczna zostanie zainicjowana raz i tylko raz
// przed uruchomieniem programu, niezależnie od wywołań
// funkcji
static int liczbaWolan = 0;
cout << "Wywolan: " << ++liczbaWolan << " argumenty: " << a << ", " << "b\n";
}
int main() {
mojaFunkcja(1, 2);
mojaFunkcja(3, 4);
mojaFunkcja(5, 6);
mojaFunkcja(7, 8);
return 0;
}
Słowo static ma także specjalne znaczenie w odniesieniu do struktur – będzie omówione później.
W przypadku słowa kluczowego extern również możemy się spodziewać dwóch znaczeń:
- jeśli poprzedza deklarację zmiennej (globalnej lub lokalnej) lub stałej, ale nie zainicjalizowanej, oznacza to typową deklarację. Można spotkać również funkcje poprzedzone tym modyfikatorem, ale nie ma on wtedy żadnego znaczenia.
- jeśli poprzedza deklarację stałej zainicjalizowanej, oznacza to, że taka stała ma być również eksportowana do innych plików. Z kolei extern przed stałą niezainicjalizowaną oznacza jej import na etapie łączenia (linkowania) programu.
Tu ważna uwaga nt. różnicy traktowania stałych przez C i C++: domyślnie zmienne globalne w C++ są w pamięci globalnej, co oznacza możliwość dostępu do nich z innych plików (jednostek kompilacji). W C taka sama sytuacja dotyczy stałych, natomiast w C++ stałe domyślnie są dostępne tylko w danym pliku (tak jakby były zadeklarowane jednocześnie z modyfikatorem static).
extern "C" {
#include <clib.h>
}