Přidělování paměti za běhu je způsob, jak programy C rostou a zmenšují se během provádění. Neznáte přesné potřeby vaší aplikace, dokud se skutečně nespustí. Řešením je použít haldu. Požadujete blokování. Operační systém si jej vyhrazuje. Ty to používáš. Poté jej vrátíte s zdarma. Tato smyčka umožňuje jiným programům znovu použít stejnou RAM.
Zvažte následující jednoduchý příklad. Toto je nejzákladnější interakce s haldou, která je možná.
Jak malloc vlastně funguje
Funkce malloc není žádná magie. Toto je mechanik, který kontroluje inventář. Když to zavoláte, proběhnou tři akce za sebou.
- Zkontroluje aktuální kapacitu haldy. Podívá se na parametr, který jste předali – v tomto případě
sizeof(int), což jsou 4 bajty. Ptá se, zda je dostatek souvislého prostoru. Pokud je halda fragmentovaná nebo plná, operace se nezdaří. - Pokud místo existuje, rezervuje tento blok. Systém jej označí jako „vaše“. Žádné další volání
mallocse tohoto fragmentu nemůže dotknout. To zabraňuje náhodným kolizím mezi alokacemi paměti. - Vrátí adresu tohoto bloku. Sama data vám neposkytne. Poskytuje vám proměnnou ukazatele. Tato proměnná obsahuje umístění. Nyní máte odkaz na konkrétní umístění v paměti RAM.
V našem příkladu obdrží p tuto adresu. Proměnná ukazatele je jednoduše kontejner pro adresu paměti. Skutečná celočíselná hodnota se nachází na této adrese.
Proč je nutné kontrolovat NULL
Kontrolu if (p == 0) uvidíte ihned po přidělení paměti. Někteří začátečníci tento krok přeskakují. To je osudová chyba.
Po každém volání malloc vždy zkontrolujte ukazatel, abyste se ujistili, že ukazatel je platný.
Velikost haldy neustále kolísá. Záleží na tom, jaké další procesy běží. Záleží na tom, kolik paměti zachytili. Neexistuje žádná záruka, že hovor bude úspěšně dokončen. Pokud malloc selže, vrátí nulu. Nula je “NULL”. Pokud se pokusíte dereferencovat nulový ukazatel, váš program se zhroutí. Tvrdý.
Můžete napsat if (p == NULL) nebo if (!p). Znamenají totéž. Ale musíte zkontrolovat. Pokaždé.
Co se stane, když zapomenete uvolnit paměť?
Pokud se váš program ukončí, aniž byste zavolali free, operační systém za vás vše vyčistí. Uvolní místo ve spustitelném souboru. Vyčistí zásobník. Vrací globální paměť. Také znovu používá všechna čekající alokace na haldě.
Znamená to tedy, že je normální únik paměti na konci programu? Technicky ano. OS se čistí. Žádné dlouhodobé následky na autě nejsou.
Ale tohle je špatná forma. A co je důležitější, úniky paměti během provádění jsou smrtící. Pokud budete alokovat paměť, aniž byste ji uvolnili, dusíte svůj vlastní proces. Hromada se plní. Nové přidělení se nezdaří. Program umírá na udušení, ne na pád.
Ukazatele vs. hodnoty, na které ukazují
Ukazatele mohou být matoucí. Liší se od hodnot, na které odkazují. Zde je návod, jak to funguje v praxi.
První scénář. Vyberete jeden blok. Ukazuješ na to dvě proměnné.
Závěr:
10
20
proč? p a q jsou různé ukazatele. Ale q = p způsobí, že ukazují na stejný blok paměti. Změnou hodnoty p se změní i na q. Změníte-li jej pomocí q, změní se také na p. Jedná se o dvě kliky jedněch dveří.
Druhý scénář. Vyberete dva samostatné bloky.
Závěr:
20
Zde p a q ukazují na různé adresy. *q obsahuje 20. *p = *q zkopíruje hodnotu 20 do místa, na které ukazuje p. Samotný ukazatel p se nepohybuje. Hodnota na adrese p se změní.
Na tomto rozlišení záleží. Jeden změní adresu uloženou v proměnné. Další mění data na této adrese. Záměna mezi těmito dvěma vede k logickým chybám, které je obtížnější ladit než syntaktické chyby.
Ukazatele, paměť a volný pád
Výstup programu: 20. Řádek 6 je samovysvětlující.
Možná si myslíte, že *p = *q a p = q dělají totéž. To je špatně. Jedna akce přesune data, druhá přesune adresy. Kompilátor umožňuje přiřazení *p = *q, protože oba dereferencované ukazatele jsou typu int. Jde o přímou výměnu významu. Ale p = q? Tohle je jiné. Vynutí, aby p ukazovalo na stejný blok paměti jako q. Adresa se přesune. Data zůstávají na svém místě.
Pokud se typy neshodují, kompilátor operaci zablokuje. Ukazatel na celé číslo nemůže ukazovat na znakový řetězec. Typy se musí shodovat.
Čtyři způsoby inicializace
Nová proměnná ukazatele je nebezpečná, dokud není inicializována.
int *p; vytvoří ukazatel, který na nic neukazuje. Ukazuje na odpadky. Dereferencování takového ukazatele je chyba. Potřebujete známé místo. Jsou čtyři způsoby, jak se tam dostat.
- malloc : Tato funkce alokuje blok v haldě.
pnyní obsahuje platnou adresu. Ukazatel je inicializován, protože ukazuje na konkrétní vyhrazenou část paměti. - Přiřazení :
p = q;pokud jeqplatné,pse stává platným. Zdědí adresuq. Pokudqobsahuje odpadky,ptaké přijímá garbage. - Explicitní adresa : Můžete ukázat
ppřímo na známou proměnnou.p = &i;přesměrujepna celé číslo ,i`. Jen. Přímo. - Nula/NULL :
p = 0;nebop = NULL;. Tím se adresa nastaví na nulu. Neukazuje na blok. Označuje nulovou polohu.
Proč používat nulu? Toto je vlajka.
Můžete zkontrolovat if (p == 0) a zjistit, zda je ukazatel platný. Systém to rozpozná. Pokud se pokusíte dereferencovat nulový ukazatel, program se zhroutí. Tvrdý.
`p = 0; *p = 5;’
Druhý řádek selže. p nic neoznačuje. Data nelze nikam zapisovat. Toto chování je kritické pro propojené seznamy později. Je to záchranná síť.
Uvolnění hromady
Alokace paměti nestačí. Musíte ji vrátit.
“malloc” zabírá místo. free to vrátí.
free(p) dělá dvě věci. Nejprve zruší rezervaci bloku. Halda tuto paměť vrátí. Dá se znovu použít. Za druhé, p zůstane neinicializováno. Nestává se magicky bezpečným. Před opětovným použitím musíte p znovu inicializovat.
Blok zmizí z vašeho zorného pole. Ukazatel začne viset.
Zde je standardní vzor pro práci s jedním celým číslem na hromadě:
Toto je v podstatě ukázka. Ale ukazuje to životní cyklus.
sizeof(int) vrací velikost v bajtech. Na většině počítačů je to 4. Můžete použít pevně zakódovanou hodnotu malloc(4). Nedělej to. sizeof činí kód přenosným. Lépe se to čte. Přizpůsobuje se změnám architektury.
malloc vrací univerzální ukazatel. Kompilátory často vydávají varování, pokud typ nepřetypujete. (int *) převede tento obecný blok na ukazatel na celé číslo. To odpovídá typu p.
free(p) vrátí blok do haldy.
Struktury a priorita operátorů
Struktury se chovají stejně. Blok přidělujete pro celou strukturu, nejen pro jednotlivá pole.
Podívejte se na (*p).i = 10;.
Proč jsou závorky potřeba?
Protože . má vyšší prioritu než *.
Bez závorek „p.i“ znamená „ (p.i)“. To se pokusí získat přístup k i přes ukazatel p a pak dereferencovat výsledek. To není to, co potřebujete. Nejprve chcete dereferencovat p a poté přistupovat k i.
Pravidla priority jsou přísná. * je v tomto kontextu spojen blíže než .. Závorky vynutí provedení dereferencování před přístupem ke členu struktury.
Většina lidí nesnáší opakované psaní (*p).i. Je to těžkopádné.
V C je zkratka.
p->i
To je přesně stejné jako (*p).i. Ale je rychlejší psát. V celém kódu C uvidíte ->. Toto je standardní způsob přístupu ke členům struktury přes ukazatel.
Hromada je omezený zdroj. Pečlivě alokujte paměť. Postupně jej uvolněte. Nebo sledujte, jak vám uniká paměť.




























