Java, Python, programowanie, ciekawostki programistyczne, algorytmy, Spring, Hibernate

poniedziałek, 8 lutego 2010

Słówko o malloc() calloc() i free()

W poprzedniej notce opisane zostało wykorzystanie wskaźników i referencji w celu modyfikacji przez funkcję pewnych zmiennych zadeklarowanych wcześniej w programie. Jakkolwiek jest to wiedza bardzo potrzebna, praktyczna i przydatna, nie wyczerpuje jednak możliwości i dziedzin zastosowania "osławionych" wskaźników.
Podczas pisania programu bardzo często wykorzystujemy tablice. Zakładamy, że czytelnik posiada elementarna wiedzę, jak to czym są i do czego one służą. Tablice w programie możemy zadeklarować w bardzo prosty sposób, jeżeli z góry znamy jej rozmiar, np.:

int tablica[10]

W tym wypadku tworzona jest 10 - elementowa tablica zmiennych typu int. Sprawa nieco się komplikuje, w momencie kiedy zależy nam na tym aby rozmiar mógł być zadeklarowany w czasie działania aplikacji, np.: po wprowadzeniu przez użytkownika pewnej liczby. Niestety rozwiązanie przedstawione poniżej nie zadziała:

int rozmiar;
scanf("%d", &rozmiar);
int tablica[rozmiar];

Powodem jest to, iż rozmiar tablicy deklarowanej w ten sposób musi być znany w trakcie kompilacji programu. Nie wnikajmy dlaczego tak się dzieje, gdyż na chwilę obecną z praktycznego punktu widzenia nie jest to aż tak ważne. Pomyślmy lepiej nad rozwiązaniem tej sytuacji. Jako, że miało być coś o wskaźnikach, nie trudno się domyślić, iż to właśnie one są odpowiednim narzędziem, które pozwoli nam rozwiązać ten problem.
To co teraz zrobimy, w przyszłości (nie, wcale nie znaczy to "od jutra", jutro to bardzo pracowity dzień, każdy z nas chyba coś od jutra zaczyna, więc w tym wypadku proponuje zacząć już od teraz) nazywać będziemy dynamiczną alokacją pamięci. Jako, że zdecydowanie wolę objaśniać napisany kod, niż pisać o tym jak go napisać, zacznijmy od kilku linijek programu. Na początek na celownik bierzemy funkcję malloc, znaną z biblioteki języka C.

int* tablica;
int wielkosc;
scanf("%d", &wielkosc);
tablica = (int*)malloc(wielkosc * sizeof(int));
...
free(tablica);
tablica = NULL;

Co my tu mamy... zacznijmy od początku. W pierwszej linijce deklarujemy wskaźnik na zmienną typu int. Druga i trzecia linia, mam nadzieję, nie wymagają wyjaśnień. To co nas najbardziej interesuje, dzieje się w linijce czwartej. Zauważmy, że mamy już wskaźnik na zmienna typu int, ale jak do tej pory nigdzie nie utworzyliśmy zmiennej, na którą mógłby on pokazywać. Czas to zmienić i w tym momencie trzeba sobie powiedzieć kilka słów o funkcji malloc. Jak można się domyślić po tym iż wynik jej działania przypisujemy do naszego wskaźnika, zwraca ona wskaźnik. Nie jest to jednak wskaźnik żadnego konkretnego typu, a konkretnie jest to wskaźnik na void. Niezbędne zatem jest rzutowanie wyniku działania funkcji na wskaźnik interesującego nas typu. Sama funkcja malloc ma za zadanie przydzielić pewien obszar pamięci, którego wielkość określamy jako parametr podczas wywołania funkcji. Zwrócony wskaźnik pokazuje na początek tego obszaru. Jak łatwo się domyślić, w naszym wypadku funkcja przydziela obszar rozmiaru ("wielkosc" * rozmiar zmiennej int) a więc rezerwuje miejsce dla wprowadzonej przez użytkownika ilości zmiennych typu int. Wiedząc, że tablica to nic innego jak ciągły obszar pamięci wielkości wielokrotności rozmiaru pojedynczej zmiennej danego typu, dochodzimy do wniosku, że właśnie utworzyliśmy tablice o zadanym przez użytkownika rozmiarze.
Od tej pory możemy jej używać w identyczny sposób, jak poznane wcześniej tablice. Trzeba jedna pamiętać aby, kiedy tablica nie jest już nam potrzeba, zwolnić przydzieloną jej pamięć. Służy do tego funkcja free, wywołana tak jak w powyższym przykładzie.

Funkcja calloc spełnia podobne zadanie, jednak podczas jej wywołania, przydzielany obszar jest inicjowany zerami. W przypadku funkcji malloc, zawartość obszaru jest nieokreślona, więc jest w nim to, co wcześniej było w tym miejscu pamięci i bynajmniej nie są to zera ;).

Bardzo dobrą, a wręcz wymaganą jest, aby po zwolnieniu pamięci, przypisać wskaźnikowi, który na nią wcześniej wskazywał, wartość NULL. Pomoże to uchronić się przed błędem ochrony pamięci, gdybyśmy przez nieuwagę w dalszej części programu spróbowali odwołać się do tego obszaru pamięci, który przecież już do nas nie należy.

W następnej notce postaram się opisać funkcje new oraz delete.

Brak komentarzy:

Prześlij komentarz