Niedziela 19 Maj 2024r. Godz 00:00:00      
Postów: 251      

Pamięć przydzielana dynamicznie C++

Czasami istnieje taki problem że nie wiemy ile pamięci będzie nam potrzebne. Na przykład mamy napisać program do sortowania x liczb. Do tego najlepiej nadaje się tablica. Ale żeby zadeklarować tablicę musimy wiedzieć jaki ma być jej rozmiar jeszcze przed skompilowaniem programu. W tedy musimy określić jej górną granicę, a nie musimy jej całej wykorzystywać. Ale pojawiają się na dwa aspekty: po pierwsze co zrobić gdy potrzebujemy większą tablicę niż mamy? i po drugie jeżeli wykorzystujemy 10 elementów z 10000 to jest to lekkie marnotractwo.

New

C++ daje nam dość proste rozwiązanie tego problemu. Najpierw pytamy się o liczbę elementów tablicy, a następnie tworzymy sobie tablicę x wymiarową, jeżeli tylko nam na to pozwala pamięć. Służy do tego operator new.

Deklaracja takich zmiennych wygląda troszeczkę inaczej niż deklaracja zwykłych zmiennych. Deklaruje się wskaźnik do pamięci przydzielonej dynamicznie.
int *zmienna1 = new int;
char *zmienna2 = new char[rozmiar];

W pierwszym przykładzie zadeklarowaliśmy sobie wskaźnik do obszaru pamięci o rozmiarze zmiennej int. Czyli po prostu przydzieliliśmy sobie pamięć dynamicznie. Natomiast w drugim przypadku zadeklarowaliśmy sobie nową tablicę której rozmiar znamy dopiero podczas wykonywania programu. Jest tutaj pewne niebezpieczeństwo. Trzeba się pilnować żeby nie odczytywać z elementu tablicy którego nie zadeklarowaliśmy. Np deklarujemy 7 elementów tablicy a odczytujemy 10. Czytamy w tedy po prostu pamięć która jest zaraz po naszej tablicy. Jeszcze gorzej jest z takim zapisem! Coś takiego może prowadzić w najlepszym przypadku do nieprawidłowego działania programu.

Dobrze ale mówiliśmy o tablicy jednowymiarowej. No a my potrzebujemy tablicę dwu wymiarową. Wpisując:

int wskaźnik = new int[12][12];

Kompilator najzwyczajniej wykaże nam błąd. Niestety takie przypisanie nie jest możliwe. Jednak dla nas programistów nie jest to rzeczą nie do ominięcia. Musimy to zrobić ręcznie. Można sobie zdefiniować tablicę n1 wymiarową z wskaźnikami typu int a tym wskaźnikom w tablicy przypisać nowe tablice typu int.
Zróbmy to w ten sposób:

void main()
{
   int **wsk;
   int n1=5,n2=6;
   wsk=new int*[n1];
   for(int i=0;iint new[n2];
   //I teraz już mamy tablicę i możemy z niej korzystać:
   wsk[2][3]=4;
}

Dobrze, potrzeba nam teraz tablicę 3 wymiarową. Niestety wymaga to troszeczkę więcej zabawy. Trzeba zadeklarować zmienną z trzema gwiazdkami, czyli wskaźnik naw wskaźnik który wskazuje na wskaźnik. Fajnie to brzmi. Następnie robimy niej więcej coś takiego:

void main()
{
   int ***wsk;
   int n1=10,n2=20,n3=30;
   wsk=new int**[n1];
   for(int i=0;inew int*[n2];
      for(int i1=0;i1new int[n3];
   }
   wsk[1][2][3]=5;
}

Myślę że konstrukcja jest zrozumiała. Tworząc odpowiednio większe tablice trzeba użyć odpowiednio więcej gwiazdek czyli wskaźników do wskaźników do... i odpowiednio więcej pętli. Ma to niestety tą wadę że nie można tego zrobić jednym poleceniem. Ma jednak swoje zalety. Można zrobić tablicę o nierównej liczbie pól. Czasami się to przydaje. Na przykład tablicę 3 wierszową gdzie 1 wiersz ma 3 kolumny 2-5, 3-10.

Przy dużych tablicach pamiętaj o sprawdzaniu pamięci, czy ci jej przypadkiem nie brakło.

Delete

Jeżeli mamy przydzieloną pamięć dynamiczną to istnieje możliwość a nawet konieczność jej zwolnienia. Jeżeli jawnie nie zwolnimy pamięci (poprzez ten operator) to fragment pamięci zostanie dla programu stracony, i pomimo że pamięci tej nie będziemy używać, system nie przydzieli jej nam drugi raz. Do zwalniania pamięci służy operator delete.
delete zmienna;
delete [] tablica;
Oba nawiasy [] mówią że wskaźnik wskazuje na tablicę danych.
Operator ten służy tylko do zwalniania pamięci przydzielonej dynamicznie. Jeżeli użyjemy tego operatora względem wskaźnika wskazującego na obiekt statyczny, np:
char *zmienna = "jakiś tekst";
delete zmienna;
to efekt takiej operacji będzie bliżej nieokreślony. Jest to tak zwane niebezpieczne użycie tego operatora.

Oczywiście zasięg pamięci nie musi obowiązywać w całym programie. Można przydzielić sobie pamięć tylko dla jednej funkcji w programie, bo akurat taka zaszła potrzeba. W tedy w funkcji2 nie możemy używać zmiennej.

 

 

Jeżeli skończy się nam pamięć to operator new zwróci nam wartość zero, czyli wskaźnik nie będzie wskazywał na żaden obiekt. Warto to sprawdzać bo może to prowadzić do błędów w programie. Przykładowy kod:

#include<iostream.h>
void main()
{
   int *wsk=new int[50000];
   if(*wsk==0)cout<<"brak pamieci";
   else cout<<"zajeto 100000 bajtow pamieci, ok 100Mb";
}