C/Wskaźniki: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
m Wycofano edycje użytkownika 78.154.84.134 (dyskusja). Autor przywróconej wersji to Wargo.
FFHJKUILIO;OP
Linia 213:
 
Należy pamiętać, że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analogiczne działania na stosie. Dodatkowo, zmienna zajmuje na stercie więcej miejsca niż na stosie - sterta utrzymuje specjalną strukturę, w której trzymane są wolne partie (może to być np. ''lista''). Tak więc używajmy dynamicznej alokacji tam, gdzie jest potrzebna - dla danych, których rozmiaru nie jesteśmy w stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem, w którym zostały zaalokowane.
 
=== Obsługa pamięci ===
Podstawową funkcją do rezerwacji pamięci jest funkcja [[C/malloc|malloc]]. Jest to niezbyt skomplikowana funkcja - podając jej rozmiar (w bajtach) potrzebnej pamięci, dostajemy wskaźnik do zaalokowanego obszaru.
 
Załóżmy, że chcemy stworzyć tablicę liczb typu float:
<source lang="C">
 
//Pamietaj aby dodac na poczatku biblioteke stdlib.h!
 
int rozmiar;
float *tablica;
rozmiar = 3;
tablica = (float*) malloc(rozmiar * sizeof(*tablica)); //pierwsza gwiazdka (*) w funkcji malloc() to operator mnozenia
tablica[0] = 0.1;
 
</source>
 
Przeanalizujmy teraz po kolei, co dzieje się w powyższym fragmencie. Najpierw deklarujemy zmienne - rozmiar tablicy i wskaźnik, który będzie wskazywał obszar w pamięci, gdzie będzie trzymana tablica. Do zmiennej "rozmiar" możemy w trakcie działania programu przypisać cokolwiek - wczytać ją z pliku, z klawiatury, obliczyć, wylosować - nie jest to istotne. <tt>rozmiar * sizeof(*tablica)</tt> oblicza potrzebną wielkość tablicy. Dla każdej zmiennej float potrzebujemy tyle bajtów, ile zajmuje ten typ danych. Ponieważ może się to różnić na rozmaitych maszynach, istnieje operator '''sizeof''', zwracający dla danego wyrażenia rozmiar jego typu w bajtach.
 
W wielu książkach (również K&Rv2) i w Internecie stosuje się inny schemat użycia funkcji malloc a mianowicie: <tt>tablica = (float*)malloc(rozmiar * sizeof(float))</tt>. Takie użycie należy traktować jako błędne, gdyż nie sprzyja ono poprawnemu wykrywaniu błędów.
 
Rozważmy sytuację, gdy programista zapomni dodać plik nagłówkowy stdlib.h, wówczas kompilator (z braku deklaracji funkcji malloc) przyjmie, że zwraca ona typ int, zatem do zmiennej <tt>tablica</tt> (która jest wskaźnikiem) będzie przypisywana liczba całkowita, co od razu spowoduje błąd kompilacji (a przynajmniej ostrzeżenie), dzięki czemu będzie można szybko poprawić kod programu. Rzutowanie jest konieczne tylko w języku C++, gdzie konwersja z <tt>void*</tt> na inne typy wskaźnikowe nie jest domyślna, ale język ten oferuje nowe sposoby alokacji pamięci.
 
Teraz rozważmy sytuację, gdy zdecydujemy się zwiększyć dokładność obliczeń i zamiast typu float użyć typu double. Będziemy musieli wyszukać wszystkie wywołania funkcji malloc, calloc i realloc odnoszące się do naszej tablicy i zmieniać wszędzie <tt>sizeof(float)</tt> na <tt>sizeof(double)</tt>. Aby temu zapobiec lepiej od razu użyć <tt>sizeof(*tablica)</tt>, wówczas zmiana typu zmiennej <tt>tablica</tt> na <tt>double*</tt> zostanie od razu uwzględniona przy alokacji pamięci.
 
Dodatkowo, należy sprawdzić, czy funkcja malloc nie zwróciła wartości NULL - dzieje się tak, gdy zabrakło pamięci. Ale uwaga: może się tak stać również jeżeli jako argument funkcji podano zero.
 
Jeśli dany obszar pamięci nie będzie już nam więcej potrzebny powinniśmy go zwolnić, aby system operacyjny mógł go przydzielić innym potrzebującym procesom.
Do zwolnienia obszaru pamięci używamy funkcji <tt>[[C/free|free()]]</tt>, która przyjmuje tylko jeden argument - wskaźnik, który otrzymaliśmy w wyniku działania funkcji <tt>malloc()</tt>.
<source lang="C">
free (tablica);
</source>
 
{{Uwaga|Należy pamiętać o zwalnianiu pamięci - inaczej dojdzie do tzw. [[C/Wskaźniki#Wycieki_pami.C4.99ci|wycieku pamięci]] - program będzie rezerwował nową pamięć, ale nie zwracał jej z powrotem i w końcu pamięci może mu zabraknąć.}}
 
Należy też uważać, by nie zwalniać dwa razy tego samego miejsca. Po wywołaniu free wskaźnik nie zmienia wartości, pamięć wskazywana przez niego może też nie od razu ulec zmianie. Czasem możemy więc korzystać ze wskaźnika (zwłaszcza czytać) po wywołaniu free nie orientując się, że robimy coś źle - i w pewnym momencie dostać komunikat o nieprawidłowym dostępie do pamięci. Z tego powodu zaraz po wywołaniu funkcji free można przypisać wskaźnikowi wartość 0.
 
Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci. Tu z pomocą przychodzi funkcja [[C/realloc|realloc]]:
<source lang="C">
tablica = realloc(tablica, 2*rozmiar*sizeof(*tablica));
</source>
 
Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub NULL gdy zabrakło pamięci). Uwaga - może to być inny wskaźnik. Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanym aktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca, funkcja znajdzie nowe miejsce i przekopiuje tam starą zawartość. Jak widać, wywołanie tej funkcji może być więc kosztowne pod względem czasu.
 
Ostatnią funkcją jest funkcja calloc(). Przyjmuje ona dwa argumenty: liczbę elementów tablicy oraz wielkość pojedynczego elementu. Podstawową różnicą pomiędzy funkcjami malloc() i calloc() jest to, że ta druga zeruje wartość przydzielonej pamięci (do wszystkich bajtów wpisuje wartość 0).
 
Przykład tworzenia tablicy typu float przy użyciu calloc() zamiast malloc():
<source lang="C">
int rozmiar;
float *tablica;
rozmiar = 3;
tablica = (float*) calloc(rozmiar, sizeof (*tablica));
tablica[0] = 0.1;
</source>
 
== Możliwe deklaracje wskaźników ==