Většina vývojářů vnímá paměť jako „černou skříňku“. Věří, že proměnné jsou prostě jména spojená s hodnotami. Toto funguje, dokud nepotřebujete napsat kód, který manipuluje s daty přímo na úrovni stroje. Pak narazíte na ukazatele.
Regulární proměnná je jednoduchá. Toto je část paměti s názvem, který používáte ve svém kódu. Deklarujte celočíselnou proměnnou i a kompilátor si vyhradí čtyři bajty. Vidíš i. Stroj vidí adresu. Hodnota je v těchto čtyřech bajtech.
Ukazatele porušují tento jednoduchý model.
Ukazatel neukládá hodnotu. Ukládá adresu. Ukazuje na jinou proměnnou. Přesněji řečeno, ukládá umístění této proměnné do paměti. Toto rozlišení je kriticky důležité. Pokud je zamícháte, váš program se zhroutí. Tvrdý.
Konstrukce je dvouúrovňová. Za prvé je tu samotná proměnná ukazatele. Obsahuje číselnou adresu. Za druhé, existuje cílová proměnná. Adresa v indexu vás zavede ke skutečným datům. Jakmile toto rozdělení pochopíte, otevřou se vám významné možnosti v systémovém programování.
Zvažte tento standardní příklad v C:
První řádek deklaruje dvě regulární celočíselné proměnné, i a j. Další řádek, int *p, deklaruje ukazatel. Hvězdička je klíčová. Říká kompilátoru, že p není celé číslo. Toto je ukazatel na celé číslo. Totéž můžete udělat s čísly s plovoucí desetinnou čárkou, strukturami, symboly nebo čímkoli jiným. Syntaxe zůstává stejná.
Pak přichází část, která často mate nováčky: p = &i;.
V jazyce C je ampersand & operátor adresy. &i nevrací hodnotu i. Vrací adresu paměti, kde se nachází i. Přiřazení p = &i zkopíruje tuto adresu do ukazatele p. Nyní p ukazuje na i.
Pokud tento krok přeskočíte, p bude obsahovat odpadky. Bude obsahovat náhodnou, nespecifikovanou adresu. Následné dereferencování pravděpodobně způsobí chybu segmentace. Program se zhroutí, protože jste mu řekli, aby měl přístup k paměti, kterou nevlastní.
Pomáhá vizualizace. Představte si paměť jako mřížku.
Před inicializací existují i, j a p, ale obsahují neznámé hodnoty. Celá čísla jsou rámečky s otazníky. Mohou obsahovat cokoliv. Ukazatel p je kruh. Má šipky ukazující v náhodných směrech. Zatím neví, kam jít.
Po provedení p = &i se šipky změní. Všechny ukazují přímo na i. Teď p přesně ví, kde je i.
Souvislost je zřejmá. p umístění prodejny. i ukládá data. Pomocí p se dostanete do i.
Tento mechanismus umožňuje C efektivně spravovat paměť. Umožňuje funkcím upravovat proměnné předávané odkazem. Poskytuje dynamickou alokaci paměti. Umožňuje vytvářet složité datové struktury, jako jsou propojené seznamy a stromy.
Ale také přináší riziko. Jedna špatná adresa a čtete nebo zapisujete data do prostoru.
Hvězdička v *p = 5 dělá něco jiného než v int *p. Druhá hvězdička byla součástí definice typu. Deklaroval proměnnou jako ukazatel. První hvězdička je operátor dereference. Znamená to “přejděte na adresu uloženou v p a změňte tam hodnotu.”
Tedy *p = 5 umístí číslo 5 do paměťového místa, na které ukazuje p. Protože p ukazuje na i, i je 5. Proměnné j je pak přiřazena hodnota i. Na obrazovce se zobrazí 5, 5 a 5. Všechny tři hodnoty odrážejí stejnou zásadní změnu.
Pochopení toho, jak fungují ukazatele v C, vyžaduje posun v myšlení. Přestaňte uvažovat o proměnných jako o kontejnerech. Začněte je považovat za místa. Ukazatel je mapa. Proměnná je cíl. Pokud je mapa špatná, jste ztraceni. A v nízkoúrovňovém programování je ztráta orientace nákladná.
Vysvětlení operátoru nepřímosti
Když přiřadíte adresu proměnné i k ukazateli p, ve skutečnosti dáváte stejnému bloku paměti dvě různá jména. Proměnná i k ní stále přistupuje přímo, ale p nyní slouží jako handle, referenční bod pro přístup ke stejným datům.
V C tato dualita definuje, jak fungují ukazatelové proměnné. Samotný ukazatel p je pouze kontejner, který ukládá adresu. Neobsahuje samotný význam, který vás zajímá. Místo toho ukládá umístění, kde se tato hodnota nachází. Zde vstupuje do hry operátor nepřímého přístupu *.
Použití *p umožňuje přístup k datům na adrese uložené v p. Tomu lze říkat dereferencování. Pokud napíšete *p = 5, nezměníte adresu uloženou uvnitř p. Přejdete na adresu, na kterou ukazuje p a tamní hodnotu přepíšete číslem 5.
Tento rozdíl je zásadní pro pochopení správy paměti. p označuje kam hledat. *p označuje, na co se chcete podívat. Záměna mezi těmito dvěma vede k chybám, které je obtížné vystopovat, protože můžete omylem změnit adresu místo dat nebo se pokusit zapsat data do náhodného paměťového místa kvůli nesprávné inicializaci p.
Jakmile zvládnete tento mentální model, ukazatele přestanou být abstraktní syntaxí a stanou se přímým kanálem pro přístup k paměti. Můžete měnit proměnné napříč rozsahem, předávat velké struktury odkazem bez jejich kopírování a vytvářet dynamické datové struktury, které se podle potřeby zvětšují a zmenšují. Je to mocný nástroj, pokud přestanete bojovat se syntaxí.
Fragment kódu níže přesně ilustruje toto chování a ukazuje, jak se hodnota i mění jen proto, že se k němu používá p.
Hodnota „i“ se aktualizuje okamžitě. Adresa se nezměnila. Změnil se pouze obsah.
Jak ukazatele ukládají hodnoty a adresy
Zdá se, že vše je jednoduché, ale mechanika práce vás může zmást. Protože proměnné i je přiřazena hodnota 5 a ukazatel p ukládá adresu i, dereferencování p (*p ) vrátí 5. Když uděláte j = i;, zkopírujete tuto hodnotu. Takže “j” se stane 5.
Nyní proveďte příkaz printf. Dostanete „5 5 5“.
To je způsobeno dvojí povahou ukazatelů. Představte si, že mají dvě různé role. Za prvé, samotná proměnná ukazatele ukládá adresu do paměti. Za druhé, ukazuje na konkrétní datový typ umístěný na této adrese.
V našem příkladu:
– p je ukazatel. Ukládá polohu do paměti.
– **p je hodnota. Toto jsou skutečná data uložená na této adrese.
Míchání těchto pojmů vede k chybám. Pokud s adresou zacházíte jako s daty, dostanete odpad. Pokud s daty zacházíte jako s adresou, váš program se zhroutí. Pochopení toho, s čím máte co do činění, všechno změní.
Proč je toto rozlišení důležité pro každodenní programování? Protože pochopení rozdílu mezi tím, kde jsou data uložena a jaká data jsou, vám umožňuje přímo manipulovat s pamětí. To je užitečné pro dynamické struktury, ale nebezpečné, pokud uděláte chybu. Jeden chybný pohyb a systém se pokusí načíst data z neexistujícího místa.
Začnete chápat, proč C zůstává tak silné, jako je nebezpečné.




















