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á správa pamäte v C ++

V minulom tutoriále 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 ++ však rozbehneme až dnes. Pochopíme totiž, ako funguje prideľovanie pamäte a vymaníme sa zo všetkých limitov dĺžky statických polí.

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 ++ 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, kompilátor vie, že na ňu má vyhradiť 32 bitov. Keď si vytvoríme poľa o 100 znakoch, C ++ 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 ++ 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 ++ 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. 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 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 ++ 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ť.

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 ++ 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.

Pozn .: 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 ++ dvojice kľúčových slov - new a delete.

New

Kľúčové slovo new povie operačnému systému o množstve pamäte, ktoré potrebuje typ, s ktorým new voláme. Funkcia vráti pointer na začiatok adresy, kde leží naša nová pamäť. Už vieme, že každý pointer má určitý typ, resp. ukazuje na nejaký typ.

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 volanie new hodnotu NULL (teda pointer nikam).

Delete

Po každom zavolaní new musí niekedy (napríklad až na konci programu) nasledovať zavolanie delete, ktorý pamäť označí 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 int a double

int* cislo = new int;
double* desetinne_cislo = new double;
//program
delete cislo;
cislo = NULL;
delete desetinne_cislo;
desetinne_cislo = NULL;

V programe s ukazovateľmi pracujeme rovnako, ako sme si ukázali v minulom diele. Na konci programu musíte pamäť uvoľniť pomocou delete. Všimnite si následného priradenia NULL späť do ukazovateľa. Pokiaľ 2x zavoláte delete na rovnaký ukazovateľ, program pravdepodobne spadne. Bude sa snažiť zmazať pamäť, ktorá mu už neprislúcha a operačný systém ho zhodí. Keď priradíme NULL, delete síce nič nezmaže, ale aspoň nám program nespadne, je teda bezpečnejšie za každým volaním delete priradiť do ukazovateľa NULL.

Č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äť neuvoľníme 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ť, pretože následná chyba nijako logicky nesúvisí s miestom v programe, kde nám pamäť pretiekla. Môže sa to prejaviť naozaj akokoľvek :)
  • 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.

Alokácie polí

Pole alokuje rovnako, ako by sa jednalo o typ. Napríklad pole 10-tich celých čísel alokuje takhle:

int* pole = new int[10];
pole[5] = 125;

Najprv si všimnime, že k ukazovateľmi môžeme skutočne pristupovať ako k poľu. Viac informácií sa dozviete v ďalšom diele. Stále platí pravidlo, že C ++ nekontroluje prekročenie poľa. C ++ nám dovolí pristúpiť napríklad na pätnásty prvok (pole [15] = 15), ale opäť sa snažíme dostať na miesto, ktoré nám nepatria. V lepšom prípade program načíta náhodné dáta, v tom horšom program spadne.

Čo sa ale líši je uvoľnenie pamäte. Tentoraz musíme kompileru povedať, že uvoľňujeme polia. Urobíme to pomocou hranatých zátvoriek za delete:

delete [] pole;

Okrúhlych zátvoriek C ++ vie, že má mazať poľa. Je tiež dôležité, aby typ ukazovatele bol rovnaký ako typ uložených dát (pretože ukazovatele môžeme pretypovať). Ak zmeníte typ ukazovatele a potom sa ho pokúsite odstrániť, bude výsledok neistý a program môže spadnúť alebo uvoľniť zlú veľkosť pamäte. V každom prípade sa program dostane do situácie, ktorá by v žiadnom prípade nemala nastať.

V budúcom dieli sa naučíme tzv. Pointerova aritmetiku a zistíme, že ukazovatele v jazyku C sú poliam ešte podobnejší, než sme si mysleli.


 

Predchádzajúci článok
Úvod do ukazovateľov v C ++
Všetky články v sekcii
Pokročilé konštrukcia C ++
Preskočiť článok
(neodporúčame)
Aritmetika ukazovateľov v C ++
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity