Hlavní strana -> Programování v C -> 14. Dynamická alokace paměti

14. Dynamická alokace paměti

Tentokráte se po dlouhé době (opravdu hodně dlouhé době, alespoň co se programování týče) vracím zpátky s dynamickou alokací paměti. Možná název sám nic neřekne, ale mohl by. Dá se usoudit, že budeme pracovat s pamětí, budeme ji moci alokovat (znamená to vyhradit si ji pro sebe), a to dynamicky.

Dynamicky alokovat paměť znamená možnost "čmajznout" si kousek paměti pro náš program ze systémové paměti. Tuto paměť dostaneme přidělenou z takzvané "haldy". Ostatní statické proměnné (ty které jsme dosud vytvářeli) se ukládaly na "zásobník". O správu zásobníku se náš program nemusí starat. A protože nemá velký smysl nechat si přidělit třebas jenom 4 byty, jako by tomu bylo u vytvoření nové celočíselné proměnné, budeme jí alokovat mnoho a procvičíme si tak také práci s poli a s ukazateli. Už teď vám můžu zaručit, že s přidělováním paměti a s hospodařením s ní si užijete tolik srandy, že to až srandovní nebude :) Už hodněkrát jsem si lámal hlavu, proč mi program padá, abych pak zjistil že... uvidíte dále.

K věci. Možnost přidělovat našemu programu paměť je velice důležitá a bez ní můžete napsat pouze velice jednoduché programy, u kterých stačí známý, konečný počet proměnných, které si vytvoříme jako programátoři sami. Představte si nejjednodušší situaci, kdy chcete napsat obyčejný textový editor. Jak byste vytvořili oblast, do které by se text ukládal? Žeby použití pole? Ano je to možnost, ale jakou mu dát velikost? Třebas 1000?

char text[1000];

Program s takovým polem znaků bude fungovat. Rozhodně ale né dlouho. Proč? Tisíc znaků je velice málo. Do takového pole se nevedje moc textu. Dali byste mu tedy velikost 1000 000? Tak velké pole už je přece dost veliké i pro rozsáhlý text... Nicméně věřte, že někdo by i toto dokázal naplnit. Pak je tady ale druhá stránka věci - obsazená paměť. Program který byste přeložili by si zabral skoro celé jedno mega operační paměti (vycházím z toho, že kilo je 1024 B a mega je 1024 KB) a přitom by uživatel využil třeba "jen" 200 kilo a zbylých 800 zůstane programem obsazených, ale nevyužitých. Jakkoli se vám může zdát moje starost o zabranou paměť o velikosti jedno mega stupidní, měli byste ji mít jako správní programátoři také. Optimalizace je mocná :-) Zabrat takovýmto programem "natvrdo" několik mega jenom kvůli toho, abychom měli alespoň částečnou jistotu, že uživateli budou stačit, je přinejmenším prasečina. Proto abychom nebyli psychopati, naučíme se dnes paměti přidělit právě tolik, kolik jí potřebujeme.

Zaprvé je nutné přidat do našeho programu hlavičkový soubor malloc.h

#include <malloc.h>

Tento obsahuje informace o funkcích, které dnes použijem. První z nich bude funkce malloc(). Tato funkce vrací ukazatel na začátek paměti, kterou uvolnila. Jejím parametrem je číslo, kolik bytů chceme alokovat. Vrácený ukazatel je třeba (možná není třeba, ale určitě je to lepší) přetypovat na takový typ dat, jakým bude paměť používána. Pro určení, kolik bajtů paměti se uvolní doporučuji používat sizeof(). Krátký příklad bude nejvhodnější.

#include <malloc.h>
.
.
int *pamet, i;

pamet = (int*)malloc(5*sizeof(int));  // tady alokujeme pamet

for(i=0; i<5; i++){
    pamet[i] = i;
}

printf("Do uvolnene pameti jsme ulozili tato cisla: ");
for(i=0; i<5; i++){
    printf("%d ", pamet[i]);
}

Nejdříve jsme vytvořili ukazatelovou proměnnou pamet, které jsme následně funkcí malloc() přidělili ukazatel na počátek paměti, kterou malloc() uvolnila. Uvnitř funkce jsem napsal 5*sizeof(int). sizeof() vrátí počet bytů, které zabírá datový typ uvedený v závorkách. Mohl jsem také napsat 5*4, protože vím, že v mém překladači je int reprezentován 4mi byty. Pokud by ale můj program byl překládán někde, kde int zabírá třebas 2byty, pak bych zbytečně uvolňoval 20 bytů paměti a využil pak pouze 10. Opačný případ by byl ještě daleko horší, protože mnou uvolněná paměť by ve skutečnosti nedostačovala pro uložení všech dat a přepisoval bych neznámou oblast. Přepisování neznámé paměti je nutné se dobře vyvarovat. Je to právě ten druh chyb, který mě již tolikrát vytrestal. Jinak ještě k tomu alokování. Je tam také přetypování ukazatele vráceného funkcí malloc() na intovský ukazatel pomocí (int*). Poté můžeme alokovanou paměť používat jako docela normální pole. První příkaz cyklu for naplní pole čísly od 0 po 4 a druhý for je vypíše na obrazovku.

Proto, aby byl program stabilnější a neobsahoval nečekané chyby je dobré ověřit si, jestli alokace proběhla úspěšně. Alokace totiž nemusí být vždy úspěšná. Může selhat například z nedostatku paměti v systému. (vím, se stránkovacím souborem to není tak lehké, ale přece ta kontrola je vhodná). Funkce malloc() vrátí při neúspěšném pokusu o alokaci paměti ukazatel NULL. Toho využijeme. Ověření úspěšnosti alokace se dá provést takto:

int *pamet;

if((pamet = (int*)malloc(5*sizeof(int))) == NULL){
    printf("Chyba pri alokaci pameti, program bude ukoncen");
    exit(0);
}

Tady se již otestuje, zda přidělení bylo úspěšné a nestane se, že bychom zapisovali do paměti, která vůbec přidělena nebyla.

Tak a teď když jsme se vyřádili, bude vhodné obsazenou paměť opět uvolnit, aby ji mohly užívat jiné programy. Žádný opravdový programátor by na tohle neměl zapomínat. K uvolnění dříve alokované paměti se použije funkce free(). Uvolnění paměti, která byla dříve alokována je vždy úspěšné, a proto není třeba free() nijak testovat. Funkci free() stačí pouze předat ukazatel na začátek uvolněné paměti, není třeba jí sdělovat velikost uvolňované paměti. Před ukončením předchozího příkladu by tedy měl následovat ještě jeden řádek:

free(pamet);

Jen bych ještě doplnil, že je opravdu nutné předat funkci free() pouze ukazatel na paměť dříve přidělenou pomocí malloc(), protože pokud budete zkoušet uvolňovat z ukazatele, který nebyl inicializován, nebo třeba ukazuje na statickou proměnnou, váš program pravděpodobně zhavaruje a jestli ne, je to jenom náhoda. Tyto "náhodné" chyby se pak těžko odstraňují. Proto si uvolňování paměti dobře hlídejte a taky doporučuju takový ukazatel po ukončení funkce free() nastavit na NULL. Váš kód bude odolnější. Ono je totiž jednoduché potom kdekoli testovat na hodnotu NULL, ale pokud to necháme v původním tvaru, pak nevíme kam ukazatel ukazuje a nemůžeme zjistit, jestli je platný. Proto uvolňujte paměť raději takto:

free(pamet);
pamet = NULL;

Další funkcí, kterou je vhodné znát v souvislosti s dynamickým přidělováním paměti je funkce realloc(), která přebírá dva parametry, prvním je ukazatal na paměť a druhým je velikost. Funkce realloc() mění velikost alokované paměti na kterou ukazuje ukazatel v prvním parametru na velikost zadanou jako druhý parametr. Může dojít ke zvětšení i ke zmenšení přidělené paměti. Při jejím zmenšení dojde samozřejmě k "ořezání" informací, které přesahují novou velikost a při zadání větší velikosti, než je současná, bude paměť rozšířena a informace ze staré paměti překopírovány, takže nedojde k jejich ztrátě. Zvětšení naší přidělené paměti můžeme udělat takto:

pamet = (int*)realloc(pamet, 10*sizeof(int));

A případně tam o5 přidat kontrolu, jestli se realokace povedla. Stejným způsobem jako u malloc()

Teď když umíme přidělit právě tolik paměti, kolik potřebujeme, už není nutné "natvrdo" zadat velikost nějakho pole s ohledem na možnost jeho přetečení. Nyní bude stačit našemu editoru přidělit zpočátku třeba 500 bytů paměti a jakmile bude tato paměť nedostatečná, přidělit dalších 500 a tak pořád dále. Program tak bude schopen efektivně využívat paměť, nebude jí zabírat zbytečně mnoho a v případě potřeby si může vzít klidně všechnu zbylou (co nechají ostatní programy ;) )

Myslím, že k vysvětlení by toto stačilo. Vše ostatní už je opět na vás, jaké vymyslíte případy, kdy by se vám toto mohlo hodit. Mějte se zatím sqěle a zdar. K.

Zpět

Programování v C | CZ 175/477 | Mapa stránek
Bc. Petr Klimánek, student Ostravské univerzity v Ostravě