Ukazatele

Z CHWiki

Přejít na: navigace, hledání

Ukazatel (anglicky pointer) je speciální druh proměnné, ve kterém je uložena "šipka", která ukazuje někam do paměti. Takže narozdíl od proměnné typu integer, kde je uloženo nějaké číslo, ukazatel obsahuje právě tu šipku (proto se mu také říká ukazatel).

Hodnotu jedné proměnné typu ukazatel můžeme zkopírovat do jiné proměnné stejného typu:

x: ukazatel;
y: ukazatel;

y := x; // nyní bude y ukazovat na stejné místo, kam ukazuje x

Všimněte si, že to funguje podobně jako s běžnými proměnnými.

Obsah

[editovat] Účel

  • Dynamická alokace paměti. Pokud chceme až za běhu rozhodnout, kolik jakých objektů budeme potřebovat, musíme použít dynamickou alokaci paměti. Když vytváříme nový objekt nebo alokujeme paměť, dostaneme zpět právě ukazatel.
  • Odkaz na objekt. Když potřebujeme se stejným objektem pracovat na více místech, použijeme na více místech ukazatel na tentýž objekt.
  • Předávání parametrů odkazem. Pokud potřebujeme umožnit funkci, aby mohla změnit parametr, předáme ho odkazem. A právě ukazatel zde funguje jako odkaz.
void GetCursorPosition(x: ukazatel na int, y: ukazatel na int) {
 // zde nějak získáme pozici
 deref(x) = 100;
 deref(y) = 130;
}

[editovat] Použití

Jak jsme si řekli, ukazatel ukazuje někam do paměti. Na tomto místě je nějaká hodnota. Pokud tedy máme ukazatel a víme, jaký typ má hodnota na místě, kam ten ukazatel ukazuje, můžeme si tuto hodnotu přečíst nebo ji změnit.

x: ukazatel na integer;
print("x ukazuje na cislo ", deref(x));  // zde vypíšeme hodnotu intu, na který ukazuje x
deref(x) := 10;     // zde zapíšeme na místo, kam x ukazuje, hodnotu 10

Všimněte si, že používáme deref(x) místo samotného x. Chceme totiž pracovat s tím, co je uloženo tam, kam ukazuje ukazatel, nechceme měnit hodnotu ukazatele samotného.

[editovat] Jak nastavit ukazatel na nějaké místo v paměti ?

  • První možnost je ukazovat tam, kde je už nějaká známá proměnná.
x: ukazatel na int;
y: int;

y := 10;
x := addressof(y);

Pak přirozeně změna hodnoty na místě, kam ukazuje ukazatel x změní obsah proměnné y, protože x ukazuje právě na y:

deref(x) = 42;
print("y ma hodnotu ", y); // vypise 42
  • Dále můžeme nechat ukazatel ukazovat na nově alokované místo v paměti. Takto můžeme za běhu vytvářet například nové instance objektů.
p: ukazatel na int;

p := new(int);
deref(p) := 42;

V této ukázce jsme si alokovali (rezervovali) místo v paměti na jeden int a do tohoto místa uložili číslo 42. Pomocí ukazatele p můžeme z tohoto místa později číst nebo tam zapisovat. Jakmile tuto paměť už nebudeme potřebovat, musíme ji uvolnit.

delete(p);
  • Poslední možnost je spíše teoretická. Mohli bychom mít ukazatel a přesně mu říct, na jakou adresu v paměti má ukazovat, ale to se v naprosté většině případů nepoužívá, protože stejně nevíme, na jakých adresách budeme v programu data mít a první dva způsoby použití ukazatelů pro normální práci stačí.

[editovat] Problémy s ukazateli

Ukazatel lze nastavit aby neukazoval nikam. To se dělá pomocí klíčového slova NULL, null, nil, Nothing, podle jazyka. Pokud ale ukazatel nikam neukazuje, nelze ho dereferencovat (snažit se číst nebo psát tam, kam ukazatel ukazuje). Následující ukázka tedy skončí s chybou:

p: ukazatel na int;
p := null;
deref(p) := 42;

Další problém může vzniknout když ukazatel ukazuje někam, kam by ukazovat neměl. Může to být buď paměť nepatřící našemu programu nebo pameť, kde má náš program uložena data, se kterými v daném okamžiku nechceme pracovat. Při zápisu na takové místo pak náš program bude sestřelen operačním systémem nebo si neúmyslně přepíšeme jiná data než bychom chtěli.

K této situaci může dojít dvěma způsoby. První z nich je vytvoření ukazatele bez inicializace. Takový ukazatel pak může ukazovat kamkoliv (typicky v jazyce C). Druhou možností je, když používáme paměť, kterou jsme již uvolnili:

p: ukazatel na int;
p := new(int);
deref(p) := 42;
// ... zde s p pracujeme
delete(p); // zde uvolníme alokovanou paměť, od tohoto okamžiku nám paměť už nepatří, p však stále ukazuje na stejné místo
deref(p) := 10; // chyba, paměť už nám nepatří

Pokud bychom měli komplikovanější kód, může se stát, že po uvolnění paměti, na kterou ukazoval p, se na stejné místo uloží jiná data. Pokud budeme potom s ukazatelem p pracovat dál jako by nebyl uvolněn, můžeme si poškodit tato data. Proto je třeba dávat si při práci s ukazateli pozor, protože překladač ani operační systém tyto chyby nekontrolují.

[editovat] Vícenásobné ukazatele

Ukazatel může ukazovat na libovolný typ. Může dokonce ukazovat i na jiný ukazatel. Můžeme tak vytvořit "řetěz" ukazatelů a až ten poslední ukazuje na hodnotu v paměti. Abychom se k té hodnotě dostali, musíme nejprve dereferencovat první ukazatel, tím získáme druhý ukazatel, ten opět dereferencujeme a tak dále, až se nakonec dostaneme k požadované hodnotě.

pp: ukazatel na ukazatel na int;
p: ukazatel na int;
i: int;

i := 10;
p := addressof(i);
pp := addressof(p);

deref(deref(pp)) := 42; // v i bude nyní 42

Vícenásobné ukazatele se nejčastěji používají při předávání ukazatele odkazem, tzn. když chceme ve funkci nastavit parametr typu ukazatel na nějakou hodnotu:

bool CreateSoundObject(SoundObject **res) {
 // pokusime se vytvorit sound object
 if (failed)
  return false;
 
 // podarilo se
 deref(res) := vytvoreny sound objekt;
 return true;
}


[editovat] Syntaxe v jazyce C

Jazyky C a C++ jsou nerozšířenější jazyky, které podporují ukazatele. Typ "ukazatel na ..." vytvoříme pomocí hvězdičky za názvem typu:

int*   // typ "ukazatel na int"
int**  // typ "ukazatel na ukazatel na int"
void*  // speciální typ, je to ukazatel bez určení typu objektu, na který ukazuje

Dereference se provádí opět pomocí hvězdičky, tentokrát před názvem ukazatele.

*x = 42; // předpokládáme, že x je ukazatel na int

Adresu objektu získáme pomocí operátoru & před objektem:

int y;
int *x = &y;   // x nyní ukazuje na y

Nový objekt v paměti vytvoříme buď funkcí malloc (v jazyce C) nebo pomocí operátoru new (v jazyce C++):

int *x = (int*)malloc(sizeof(int));
int *x = new int;

Mazání paměti provedeme pomocí funkce free (C) nebo operátorem delete (C++):

free(x);
delete x;

Ukazatele dále podporuje Pascal, jistým způsobem i C# a další jazyky.