Podręcznik
1. Wskaźniki, tablice i listy dynamiczne
W trakcie nauki programowania na początku przedstawiane są typu zmiennych jakiego typu mogą być zmienne w języku C++. Teraz poruszymy zagadnienia związane z zapisem zmiennych w pamięci oraz przedstawimy typy ze względu na miejsce zapisu oraz ich zasięg w aplikacji. W języku C++ zmienne są przechowywane w różnych segmentach pamięci, w zależności od ich typu, czasu życia i sposobu alokacji. Ogólnie, pamięć procesu można podzielić na kilka segmentów:- Stos (ang. stack) - przechowuje zmienne lokalne (automatyczne, bez static), wskaźniki oraz informacje o wywołaniach funkcji (stos wywołań).
- Sterta (ang. heap) - przechowuje zmienne dynamiczne alokowane przez operator
new
. - Segment danych statycznych (ang. data segment) - podzielony jest na dwie części:
- Zainicjalizowane zmienne statyczne (ang. Initialized Data Segment) - przechowuje zmienne globalne i statyczne, które są zainicjalizowane przed uruchomieniem aplikacji.
- Niezainicjalizowane dane statyczne (ang. Block Started by Symbol - BSS) - przechowuje zmienne globalne i statyczne, które nie zostały jawnie zainicjalizowane.
- Segment kodu (ang. Text Segment) - przechowuje kod wykonywalny programu (instrukcje maszynowe).
Sterta rośnie w górę, a pamięć jest przydzielana i zwalniana dynamicznie. Jeżeli sterta zostanie w pełni zapełniona, próba alokacji pamięci może spowodować błąd (brak dostępnej pamięci). Stos natomiast rośnie w dół, zmniejszając adresy pamięci. Przy każdym wywołaniu na stosie zapisywane są lokalne zmienne i adres powrotu. Przepełnienie stosu może wystąpić w przypadku głębokiej rekurencji lub alokacji dużych zmiennych lokalnych.
Znajomość tego schematu pamięci jest kluczowa przy programowaniu w C++, zwłaszcza jeśli chodzi o zarządzanie dynamiczną pamięcią, optymalizację programu oraz unikanie błędów, takich jak wycieki pamięci lub przepełnienie stosu.W języku C++ istnieje możliwość podziału zmiennych uwzględniając różne kryteria klasyfikacji, takie jak np. zasięg, czas życia lub sposób alokacji pamięci.
Ze względu na zasięg możemy podzielić zmienne na:
- Zmienne globalne - są one deklarowane poza funkcjami lub klasami. Są widoczne w całym pliku źródłowym i mogą być widoczne w innych plikach, jeżeli nie są zadeklarowane jako static.
- Zmienne lokalne - są deklarowane wewnątrz funkcji, bloków kodu lub metod. Dostępne są jedynie w zakresie danego bloku lub funkcji w której zostały zadeklarowane.
- Zmienne statyczne globalne - są deklarowane poza funkcjami z użyciem słowa kluczowego static. Inicjalizowane są na początku programu i istnieją przez cały czas jego działania, ale mają ograniczony zasięg do pliku, w którym zostały zadeklarowane.
- Zmienne statyczne lokalne - są deklarowane wewnątrz funkcji lub metod z użyciem słowa kluczowego static. Widoczne są tylko w funkcji, w której były zadeklarowane ale ich czas życia jest taki sam jak czas trwania programu. Ich wartość nie jest resetowana przy kolejnych wywołaniach funkcji.
- Zmienne automatyczne - są deklarowane wewnątrz funkcji lub bloku (bez słowa kluczowego static). Alokowane są na stosie i zwalniane automatycznie po zakończeniu działania bloku/funkcji.
- Zmienne statyczne - pamięć na tego typu zmienne przydzielana jest na początku działania programu oraz zwalniana po jego zakończeniu. Mogą być zadeklarowane jako globalne (statyczna globalna) lub lokalnie w funkcji, gdzie zachowują wartość między wywołaniami funkcji.
- Zmienne dynamiczne - tworzone są w trakcie działania programu przez dynamiczną alokacje pamięci (na stercie) np. za pomocą operatora
new
. Ich czas życia zależy od tego, kiedy są jawnie zwlaniane przez programistę np. za pomocą operatordelete
.
Możemy także podzielić zmienne ze względu na miejsce alokacji pamięci:
- Zmienne na stosie (ang. stack) - dotyczy zmiennych automatycznych. Pamięć jest przydzielana automatycznie gdy zmienna jest deklarowana i zwalniana gdy zmienna wychodzi z zakresu.
- Zmienne na stercie (ang. heap) - dotyczy zmiennych dynamicznych alokowanych za pomocą operatora
new
. Pamięć musi zostać zwolniona przez programistę. - Zmienne w sekcji danych statycznych (ang. static data segment) - dotyczy zmiennych globalnych oraz statycznych. Pamięć jest przydzielana na poczatku programu i zwlaniana po jego zakończeniu.
W wiekszości zastosowań praktycznych istnieje potrzeba deklarowania zmiennych w trakcie działania aplikacji tzn. zmiennych dynamicznych.
Po co więc stosujemy zmienne dynamiczne:
- umożliwiają tworzenie bardzo dużych tablic,
- umożliwiają tworzenie struktur dynamicznych o nie znanej z góry wielkości (listy dynamiczne, drzewa),
- można je w każdej chwili podczas działania programu usunąć z pamięci i na zwolnionym miejscu utworzyć nową zmienną dynamiczną - w tym samym lub innym programie. Ułatwiają więc równoległą pracę wielu programów w systemie operacyjnym.
Do zmiennych dynamicznych mamy dostęp przez ich adres, nazwany wskaźnikiem. Adres w C++ to lokalizacja (numer) w pamięci, gdzie przechowywana jest określona wartość lub obiekt. Każda zmienna w programie ma swój adres, który wskazuje, gdzie w pamięci została przechowana.