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

Dodane 360 bajtów ,  2 lata temu
m
Update syntaxhighlight tags - remove use of deprecated <source> tags
m (→‎Zobacz też: nowy link)
m (Update syntaxhighlight tags - remove use of deprecated <source> tags)
By stworzyć wskaźnik do zmiennej i móc się nim posługiwać, należy przypisać mu odpowiednią wartość - adres obiektu, na jaki chcieliśmy aby wskazywał. Skąd mamy znać ten adres? W języku C możemy "zapytać się" o adres za pomocą operatora '''&''' (operatora pobrania adresu). Przeanalizuj następujący kod:
 
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
return 0;
}
</syntaxhighlight>
</source>
Program ten wypisuje adres pamięci, pod którym znajduje się zmienna oraz wartość jaką kryje zmienna przechowywana pod owym adresem. Przykładowy wynik:
Wartość zmiennej liczba: 80
Aby móc przechowywać taki adres, zadeklarujemy zmienną wskaźnikową. Ważną informacją, oprócz samego '''adresu wskazywanej zmiennej''', jest '''typ wskazywanej zmiennej'''. Mimo że wskaźnik jest zawsze typu adresowego, kompilator wymaga od nas, abyśmy przy deklaracji podali typ zmiennej, na którą wskaźnik będzie wskazywał. Robi się to poprzez dodanie '''*''' (gwiazdki) przed nazwą wskaźnika, np.:
 
<sourcesyntaxhighlight lang="C">
int *wskaznik1; // zmienna wskaźnikowa na obiekt typu liczba całkowita
char *wskaznik2; // zmienna wskaźnikowa na obiekt typu znak
float *wskaznik3; // zmienna wskaźnikowa na obiekt typu liczba zmiennoprzecinkowa
</syntaxhighlight>
</source>
 
{{Uwaga|
 
Niektórzy programiści mogą nieco błędnie interpretować wskaźnik do typu jako nowy typ i uważać, że jeśli napiszą:
<sourcesyntaxhighlight lang="C">
int * a,b,c;
</syntaxhighlight>
</source>
to otrzymają trzy wskaźniki do liczby całkowitej. W rzeczywistości uzyskamy jednak tylko jeden wskaźnik a, oraz dwie liczby całkowite b i c (tak jakbyśmy napisali int *a; int b, int c). W tym przypadku trzy wskaźniki otrzymamy pisząc:
<sourcesyntaxhighlight lang="C">
int *a,*b,*c;
</syntaxhighlight>
</source>
Aby uniknąć pomyłek, lepiej jest pisać gwiazdkę tuż przy zmiennej, albo jeszcze lepiej - nie mieszać deklaracji wskaźników i zmiennych:
<sourcesyntaxhighlight lang="C">
int *a;
int b,c;
</syntaxhighlight>
</source>
 
== Dostęp do wskazywanego obiektu ==
Aby dobrać się do wartości wskazywanej przez wskaźnik, należy użyć unarnego operatora '''<code>*</code>''' (gwiazdka), zwanego '''operatorem wyłuskania'''. Mimo, że kolejny raz używamy gwiazdki, oznacza ona teraz coś zupełnie innego. Jest tak, ponieważ używamy jej w zupełnie innym miejscu: nie przy deklaracji zmiennej (gdzie gwiazdka oznacza deklarowanie wskaźnika), a przy '''wykorzystaniu''' zmiennej, gdzie odgrywa rolę operatora, podobnie jak operator & (pobrania adresu obiektu). Program ilustrujący:
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
return 0;
}
</syntaxhighlight>
</source>
 
Przykładowy wynik programu:
== Gdy argument jest wskaźnikiem... ==
Czasami zdarza się, że argumentami funkcji są wskaźniki. W przypadku zwykłych zmiennych, nasza funkcja otrzymuje jedynie lokalne kopie argumentów, które zostały jej podane. Wszelkie zmiany dokonują się lokalnie i nie są widziane poza funkcją. Przekazując do funkcji wskaźnik, również zostaje stworzona kopia... wskaźnika, na którym możemy operować. Tu jednak kopiowanie i niewidoczne lokalne zmiany się kończą. Obiekt, na który wskazuje ten wskaźnik, znajduje się gdzieś w pamięci i możemy na nim działać (czyli na oryginale), tak więc zmiany te są widoczne po wyjściu z funkcji. Spróbujmy rozpatrzyć poniższy przykład:
<sourcesyntaxhighlight lang="C">
#include <stdio.h>
return 0;
}
</syntaxhighlight>
</source>
 
Wynikiem będzie:
==Pułapki wskaźników==
Ważne jest, aby przy posługiwaniu się wskaźnikami nigdy nie próbować odwoływać się do komórki wskazywanej przez wskaźnik o wartości NULL ani nie używać niezainicjowanego wskaźnika! Przykładem nieprawidłowego kodu może być np.:
<sourcesyntaxhighlight lang="C">
int *wsk;
printf ("zawartosc komorki: %d\n", *(wsk)); /* Błąd */
wsk = NULL;
printf ("zawartosc komorki: %d\n", *(wsk)); /* Błąd */
</syntaxhighlight>
</source>
 
Pamiętaj też, że możesz być rozczarowany używając operatora sizeof, podając zmienną wskaźnikową. Uzyskana wielkość będzie oznaczała rozmiar adresu, a nie rozmiar typu użytego podczas deklarowania naszego wskaźnika. Wielkość ta będzie zawsze miała taki sam rozmiar dla każdego wskaźnika, w zależności od kompilatora, a także docelowej platformy. Zamiast tego używaj: ''sizeof(*wskaźnik)''. Przykład:
<sourcesyntaxhighlight lang="C">
char *zmienna;
int z = sizeof zmienna; /* z może być równe 4 (rozmiar adresu na maszynie 32bit) */
z = sizeof *zmienna; /* tym razem z= rozmiar znaku, tj. 1 */
z = sizeof(char); /* robimy to samo, co wyżej */
</syntaxhighlight>
</source>
 
== Stałe wskaźniki ==
Podobnie jak możemy deklarować zwykłe stałe, tak samo możemy mieć stałe wskaźniki - jednak są ich dwa rodzaje. Wskaźniki na stałą wartość:
<sourcesyntaxhighlight lang="C">
const int *a;
int const * a; /* równoważnie */
</syntaxhighlight>
</source>
 
oraz stałe wskaźniki:
<sourcesyntaxhighlight lang="C">
int * const b;
</syntaxhighlight>
</source>
 
Słówko <tt>const</tt> przed typem działa jak w przypadku zwykłych stałych, tzn. nie możemy zmienić wartości wskazywanej przy pomocy wskaźnika.
Obie opcje można połączyć, deklarując stały wskaźnik, którym nie można zmienić wartości wskazywanej zmiennej, i również można zrobić to na dwa sposoby:
 
<sourcesyntaxhighlight lang="C">
const int * const c;
int const * const c; /* równoważnie */
b = a; /* kompilator zaprotestuje */
c = a; /* kompilator zaprotestuje */
</syntaxhighlight>
</source>
 
Wskaźniki na stałą wartość są przydatne między innymi w sytuacji gdy mamy duży obiekt (na przykład [[C/Typy złożone|strukturę]] z kilkoma polami). Jeśli przypiszemy taką zmienną do innej zmiennej, kopiowanie może potrwać dużo czasu, a oprócz tego zostanie zajęte dużo pamięci. Przekazanie takiej struktury do funkcji albo zwrócenie jej jako wartość funkcji wiąże się z takim samym narzutem. W takim wypadku dobrze jest użyć wskaźnika na stałą wartość.
<sourcesyntaxhighlight lang="C">
void funkcja(const duza_struktura *ds)
{
....
funkcja(&dane); /* mamy pewność, że zmienna dane nie zostanie zmieniona */
</syntaxhighlight>
</source>
 
==Dynamiczna alokacja pamięci==
 
Załóżmy, że chcemy stworzyć tablicę liczb typu float:
<sourcesyntaxhighlight lang="C">
 
//Pamietaj aby dodac na poczatku biblioteke stdlib.h!
tablica[0] = 0.1;
 
</syntaxhighlight>
</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.
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>.
<sourcesyntaxhighlight lang="C">
free (tablica);
</syntaxhighlight>
</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ąć.}}
 
Czasami możemy potrzebować zmienić rozmiar już przydzielonego bloku pamięci. Tu z pomocą przychodzi funkcja [[C/realloc|realloc]]:
<sourcesyntaxhighlight lang="C">
tablica = realloc(tablica, 2*rozmiar*sizeof(*tablica));
</syntaxhighlight>
</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.
 
Przykład tworzenia tablicy typu float przy użyciu calloc() zamiast malloc():
<sourcesyntaxhighlight lang="C">
int rozmiar;
float *tablica;
tablica = (float*) calloc(rozmiar, sizeof (*tablica));
tablica[0] = 0.1;
</syntaxhighlight>
</source>
 
== Możliwe deklaracje wskaźników ==
Tutaj znajduje się krótkie kompendium jak definiować wskaźniki oraz co oznaczają poszczególne definicje:
<sourcesyntaxhighlight lang="C">
int i; /* zmienna całkowita (typu int) 'i' */
int *p; /* wskaźnik 'p' wskazujący na zmienną całkowitą */
int (*fpa())[];/* funkcja 'fpa', która zwraca wskaźnik na tablicę liczb typu int */
int (*fpf())();/* funkcja 'fpf', która zwraca wskaźnik na funkcję, która zwraca dane typu int */
</syntaxhighlight>
</source>
 
== Popularne błędy ==
Przykład funkcji powodującej wyciek pamięci (tworzy wskaźnik, przydziela pamięć i nie zwalnia pamięci po zakończeniu funkcji): <ref>[http://www.geeksforgeeks.org/what-is-memory-leak-how-can-we-avoid/ What is Memory Leak? How can we avoid? February 6, 2010]</ref>
 
<sourcesyntaxhighlight lang=c>
/*
Function with memory leak
return 0 ; /* Return without freeing ptr*/
}
</syntaxhighlight>
</source>
 
Sprawdzamy za pomocą [[Programowanie w systemie UNIX/Valgrind|Valgrinda]]
Powinno być:
 
<sourcesyntaxhighlight lang=c>
/*
Function without memory leak
return 0;
}
</syntaxhighlight>
</source>
 
 
90

edycji