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:
  1. Stos (ang. stack) - przechowuje zmienne lokalne (automatyczne, bez static), wskaźniki oraz informacje o wywołaniach funkcji (stos wywołań).
  2. Sterta (ang. heap) - przechowuje zmienne dynamiczne alokowane przez operator new.
  3. 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.
  4. Segment kodu (ang. Text Segment) - przechowuje kod wykonywalny programu (instrukcje maszynowe).
Poniżej na rysunku przedstawiono rozmieszczenie poszczególnych segmentów w pamięci komputera.

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:
  1. 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.
  2. 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.
  3. 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.
  4. 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.
Ze względu na czas życia możemy podzielić zmienne na:
  1. 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.
  2. 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.
  3. 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ą operator delete.

Możemy także podzielić zmienne ze względu na miejsce alokacji pamięci:

  1. Zmienne na stosie (ang. stack) - dotyczy zmiennych automatycznych. Pamięć jest przydzielana  automatycznie gdy zmienna jest deklarowana i zwalniana gdy zmienna wychodzi z zakresu.
  2. Zmienne na stercie (ang. heap) - dotyczy zmiennych dynamicznych alokowanych za pomocą operatora new. Pamięć musi zostać zwolniona przez programistę.
  3. 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.