IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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

 

Predchádzajúci článok
Úvod do ukazovateľov (pointer) v jazyku C
Všetky články v sekcii
Dynamická práca s pamäťou v jazyku C
Preskočiť článok
(neodporúčame)
Aritmetika ukazovateľov v jazyku C
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
3 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity