2. diel - Dynamická alokácia pamäte v jazyku C
V minulej lekcii, Úvod do ukazovateľov (pointer) v jazyku C , sme si uviedli Pointer v jazyku C. Už vieme, že nás jazyk C nechá pracovať s pamäťou a naučili sme sa odovzdávať parametre funkcií referencií. To pravé programovanie v céčko však rozbehneme až v dnešnom tutoriálu. Pochopíme totiž, ako funguje prideľovanie pamäte a vymaníme sa zo všetkých limitov dĺžky statických polí a reťazcov.
Statická a dynamická alokácia pamäte
Ako vieme, o pamäť si musí náš program hovoriť operačnému systému, čo nie je úplne jednoduché a preto sa toho Céčko snažia urobiť čo najviac za nás.
Staticky alokovaná pamäť
Keď sa náš program prekladá, môže prekladač vo veľkom množstve
prípadov jednoducho zistiť, koľko pamäti bude pri behu programu treba. Keď
si vytvoríme premennú typu int
, Céčko vie, že na ňu má
vyhradiť 32 bitov. Keď si vytvoríme poľa o 100 znakoch, Céčko opäť vie,
že má rezervovať 800 bitov. Pokiaľ nie je pri behu programu treba žiadne
dáta pridávať, s touto automatickou alokáciou si bohato vystačíme. To je v
podstate spôsob, akým sme programovali doteraz.
Dynamicky alokovaná pamäť v zásobníku
Možno vás napadlo, ako v jazyku C funguje prideľovanie pamäte pre
lokálne premenné (to sú tie definované vnútri funkcií). Céčko predsa
nevie koľkokrát funkciu zavoláme a teda koľko premenných bude vo finále
potreba. Táto pamäť je naozaj prideľovaná dynamicky až za
behu programu. Všetko sa však deje zas plne automaticky. Akonáhle funkciu
zavoláme, Céčko si povie o pamäť a akonáhle funkcie skončí, táto
pamäť sa uvoľní. Toto je dôvod, prečo funkcia nemôže vracať poľa v nej
vytvorené. Ako už vieme, pole sa totiž nekopíruje (neodovzdáva hodnotou ako
napríklad int
), ale pracuje sa s ním ako by to bol ukazovateľ.
Keďže lokálne premenné po skončení funkcie zaniknú, získali by sme
ukazovateľ niekam, kde žiadne pole už nemusí existovať.
Dynamicky alokovaná pamäť v halde
Zatiaľ to vyzerá, že Céčko robí všetko za nás. Kde je teda problém? Predstavte si, že programujete aplikáciu, ktorá eviduje napr. Položky v sklade. Viete dopredu, ako veľké pole pre položky vytvoriť? Bude ich 100, 1000, milióny? Ak budeme deklarovať statické pole nejakých štruktúr, vždy budeme buď plytvať miestom alebo sa vystavíme nebezpečenstvu, že nám vyhradenej pole prestane stačiť. A nemusíme ani zachádzať do tejto situácie, stačí popremýšľať nad tým, ako uložiť textový reťazec presne tak dlhý, ako ho používateľ zadal.
Problém teda tkvie v tom, že niekedy do spustenia programu nevieme koľko pamäti bude treba a preto ju za nás Céčko nemôže alokovať. Našťastie nám však ponúka funkcie, ktorými si môžeme za behu programu hovoriť o ľubovoľné množstvo pamäte.
V texte boli spomenuté pojmy zásobník (stack) a halda (heap). Jedná sa o 2 typy pamäti v RAM, s ktorými program pracuje. Zjednodušene môžeme povedať, že práca so zásobníkom je rýchlejší, ale je veľkostne obmedzený. Halda je určená primárne pre väčšie dáta, napr. Pre spomínané položky v sklade. Keď si budeme hovoriť o pamäť my sami, bude pridelená vždy na halde.
Dynamická alokácia pamäte
Ťažiskom práce s dynamickou pamäťou je v jazyku C dvojica funkcií - malloc () a free ().
Malloc ()
malloc()
povie operačnému systému o ľubovoľné množstvo
pamäte (koľko jej potrebujeme uvedieme do parametra funkcie v
bajtoch). Funkcia vráti pointer na prvú adresu, kde začína
naša nová pamäť. Už vieme, že každý pointer má určitý typ, resp.
ukazuje na dáta nejakého typu. Aby bola funkcia malloc()
univerzálne, vracia pointer na typ void
. Jej výsledok by sme mali
vždy pretypovať na taký pointer, ktorý potrebujeme.
Ak sa alokácia pamäte nepodarí (napr. Nám došla, čo sa dnes už
teoreticky nestane, ale mali by sme s týmto prípadom počítať), vráti
malloc()
hodnotu NULL
(teda pointer nikam). Pri
alokovanie pamäte budeme vždy používať funkciu sizeof()
,
pretože nikdy nevieme ako je dátový typ na danom operačnom systéme veľký
(napr. int
môže zaberať 16 i 32 bitov). Volanie
sizeof()
sú nahradená konštantami pri kompilácii, takže nijako
negatívne neovplyvňujú rýchlosť programu.
Free ()
Po každom zavolaní funkcie malloc()
musí niekedy (napríklad
až na konci programu) nasledovať zavolaní funkcie free()
, ktorá
pamäť označí opäť ako voľnú. Táto pamäť je plne v našej réžií a
nikto iný ako my ju za nás neuvoľní. Akonáhle prestaneme nejakú dynamicky
alokovanú pamäť potrebovať, mali by sme ju hneď uvoľniť.
Vyhraďte si za behu programu miesto pre 100 int
ov:
int main(int argc, char** argv) { int *p_i; printf("Pokouším se alokovat paměť pro 100 intů.\n"); // Alokace 100 krát velikosti intu p_i = (int *) malloc(sizeof(int) * 100); // Kontrola úspěšnosti alokace if (p_i == NULL) { printf("Nedostatek paměti.\n"); exit(1); } // Uvolnění paměti printf("Uvolňuji paměť.\n"); free(p_i); p_i = NULL; // Pro jistotu vynullujeme ukazatel return (EXIT_SUCCESS); }
výstup:
c_malloc
Pokouším se alokovat paměť pro 100 intů.
Uvolňuji paměť.
Zatiaľ ešte nevieme ako k jednotlivým int
om v pamäti
pristupovať, všetko si vysvetlíme hneď nabudúce. Funkcia
exit()
ukončí našu aplikáciu. V parametri odovzdáme chybový
kód, ktorý by mal byť v prípade že program nedobehol správne,
nenulový.
Časté chyby pri práci s Pointer
Práca s ukazovateľmi je pomerne nebezpečná, pretože nás programátorov pri nej nikto nestráži. A urobiť chybu v programe je veľmi jednoduché a to človek ani nemusí byť začiatočníkom. Spomeňme si niekoľko bodov, na ktoré je dobré pri práci s ukazovateľmi dávať pozor.
- Neuvoľnení pamäti - Ak raz zabudneme uvoľniť nejakú pamäť, tak sa v zásade nič nestane. Problém je v prípade, keď pamäť zabudneme uvoľniť vnútri nejakej funkcie, ktorá sa za behu programu volá niekoľkokrát. Najhoršia situácia je, keď pamäť zabudneme uvoľniť v nejakom cykle. Pamäť nám pri tejto chybe samozrejme za nejakú dobu dôjde, aplikácia spadne a užívateľ príde o dáta a zaplatí radšej konkurenciu, aby mu predala funkčnú aplikáciu
- Prekročenie hraníc pamäte - Rovnako ako tomu bolo u polí, ani u pointer nikto nestráži čo do tejto pamäte ukladáme. Pokiaľ uložíme niečo väčšieho, než koľko miesta máme rezervované, naburame pamäť inej časti aplikácie. Táto chyba sa môže prejaviť úplne kdekoľvek a pravdepodobne ju budeme veľmi dlho hľadať. Následná chyba totiž nijako logicky nesúvisí s miestom v programe, kde nám pamäť pretiekla. Môže sa zrazu rozbiť akákoľvek časť aplikácie, pretože do nej táto pamäť vytiekla
- Práca s uvoľnenou pamäťou - Môže sa nám stať, že
nejakú pamäť uvoľníme a potom sa na túto adresu pokúsime znova niečo
zapísať. V tej chvíli však zapisujeme opäť na pamäť, ktorá nám
nepatrí, následky viď. minulý bod. Preto je dobré uložiť do ukazovateľa
po uvoľnení jeho pamäti hodnotu
NULL
, aby sme sa tejto chybe vyvarovali.
V budúcej lekcii, Aritmetika ukazovateľov v jazyku C , sa naučíme tzv. Pointerova aritmetiku a zistíme, že ukazovatele v jazyku C sú poliam ešte podobnejší, než sme si mysleli.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 137x (31.81 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C