Java týden Body zdarma
Využij podzimních slev a získej od nás až 40 % bodů zdarma! Více zde
Pouze tento týden sleva až 80 % na Java e-learning!

Lekce 2 - Dynamická alokace paměti v jazyce C

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Úvod do ukazatelů (pointerů) v jazyce C, jsme si uvedli pointery v jazyce C. Již víme, že nás jazyk C nechá pracovat s pamětí a naučili jsme se předávat parametry funkcí referencí. To pravé programování v Céčku ovšem rozjedeme až v dnešním tutoriálu. Pochopíme totiž, jak funguje přidělování paměti a vymaníme se ze všech limitů délky statických polí a řetězců.

Statická a dynamická alokace paměti

Jak víme, o paměť si musí náš program říkat operačnímu systému, což není úplně snadné a proto se toho Céčko snaží udělat co nejvíce za nás.

Staticky alokovaná paměť

Když se náš program překládá, může překladač ve velkém množství případů jednoduše zjistit, kolik paměti bude při běhu programu třeba. Když si vytvoříme proměnnou typu int, Céčko ví, že na ni má vyhradit 32 bitů. Když si vytvoříme pole o 100 znacích, Céčko opět ví, že má rezervovat 800 bitů. Pokud není při běhu programu třeba žádná data přidávat, s touto automatickou alokací si bohatě vystačíme. To je v podstatě způsob, jakým jsme programovali doposud.

Dynamicky alokovaná paměť v zásobníku

Možná vás napadlo, jak v jazyce C funguje přidělování paměti pro lokální proměnné (to jsou ty definované uvnitř funkcí). Céčko přeci neví kolikrát funkci zavoláme a tedy kolik proměnných bude ve finále potřeba. Tato paměť je opravdu přidělována dynamicky až za běhu programu. Vše se ovšem děje zas plně automaticky. Jakmile funkci zavoláme, Céčko si řekne o paměť a jakmile funkce skončí, tato paměť se uvolní. Toto je důvod, proč funkce nemůže vracet pole v ní vytvořené. Jak již víme, pole se totiž nekopíruje (nepředává hodnotou jako třeba int), ale pracuje se s ním jako by to byl ukazatel. Jelikož lokální proměnné po skončení funkce zaniknou, získali bychom ukazatel někam, kde žádné pole již nemusí existovat.

Dynamicky alokovaná paměť v haldě

Zatím to vypadá, že Céčko dělá vše za nás. Kde je tedy problém? Představte si, že programujete aplikaci, která eviduje např. položky ve skladu. Víte dopředu, jak velké pole pro položky vytvořit? Bude jich 100, 1000, miliony? Pokud budeme deklarovat statické pole nějakých struktur, vždy budeme buď plýtvat místem nebo se vystavíme nebezpečí, že nám vyhrazené pole přestane stačit. A nemusíme ani zacházet do této situace, stačí zapřemýšlet nad tím, jak uložit textový řetězec přesně tak dlouhý, jak ho uživatel zadal.

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

Problém tedy tkví v tom, že někdy do spuštění programu nevíme kolik paměti bude třeba a proto ji za nás Céčko nemůže alokovat. Naštěstí nám ovšem nabízí funkce, kterými si můžeme za běhu programu říkat o libovolné množství paměti.

V textu byly zmíněny pojmy zásobník (stack) a halda (heap). Jedná se o 2 typy paměti v RAM, se kterými program pracuje. Zjednodušeně můžeme říci, že práce se zásobníkem je rychlejší, ale je velikostně omezený. Halda je určena primárně pro větší data, např. pro zmíněné položky ve skladu. Když si budeme říkat o paměť my sami, bude přidělena vždy na haldě.

Dynamická alokace paměti

Těžištěm práce s dynamickou pamětí je v jazyce C dvojice funkcí - malloc() a free().

malloc()

malloc() řekne operačnímu systému o libovolné množství paměti (kolik jí potřebujeme uvedeme do parametru funkce v bajtech). Funkce vrátí pointer na první adresu, kde začíná naše nová paměť. Již víme, že každý pointer má určitý typ, resp. ukazuje na data nějakého typu. Aby byla funkce malloc() univerzální, vrací pointer na typ void. Její výsledek bychom měli vždy přetypovat na takový pointer, který potřebujeme.

Pokud se alokace paměti nepodaří (např. nám došla, což se dnes již teoreticky nestane, ale měli bychom s tímto případem počítat), vrátí malloc() hodnotu NULL (tedy pointer nikam). Při alokování paměti budeme vždy používat funkci sizeof(), protože nikdy nevíme jak je datový typ na daném operačním systému velký (např. int může zabírat 16 i 32 bitů). Volání sizeof() jsou nahrazena konstantami při kompilaci, takže nijak negativně neovlivňují rychlost programu.

free()

Po každém zavolání funkce malloc() musí někdy (třeba až na konci programu) následovat zavolání funkce free(), která paměť označí opět jako volnou. Tato paměť je plně v naší režií a nikdo jiný než my ji za nás neuvolní. Jakmile přestaneme nějakou dynamicky alokovanou paměť potřebovat, měli bychom ji ihned uvolnit.

Alokujme si za běhu programu místo pro 100 intů:

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ěť.

Zatím ještě nevíme jak k jednotlivým intům v paměti přistupovat, vše si vysvětlíme hned příště. Funkce exit() ukončí naši aplikaci. V parametru předáme chybový kód, který by měl být v případě že program nedoběhl správně, nenulový.

Časté chyby při práci s pointery

Práce s ukazateli je poměrně nebezpečná, jelikož nás programátory při ní nikdo nehlídá. A udělat chybu v programu je velmi jednoduché a to člověk ani nemusí být začátečníkem. Zmiňme si několik bodů, na které je dobré při práci s ukazateli dávat pozor.

  • Neuvolnění paměti - Pokud jednou zapomeneme uvolnit nějakou paměť, tak se v zásadě nic nestane. Problém je v případě, kdy paměť zapomeneme uvolnit uvnitř nějaké funkce, která se za běhu programu volá několikrát. Nejhorší situace je, když paměť zapomeneme uvolnit v nějakém cyklu. Paměť nám při této chybě samozřejmě za nějakou dobu dojde, aplikace spadne a uživatel přijde o data a zaplatí raději konkurenci, aby mu prodala funkční aplikaci :)
  • Překročení hranic paměti - Stejně jako tomu bylo u polí, ani u pointerů nikdo nehlídá co do této paměti ukládáme. Pokud uložíme něco většího, než kolik místa máme vyhrazeno, nabouráme paměť jiné části aplikace. Tato chyba se může projevit naprosto kdekoli a pravděpodobně ji budeme velmi dlouho hledat. Následná chyba totiž nijak logicky nesouvisí s místem v programu, kde nám paměť přetekla. Může se najednou rozbít jakákoli část aplikace, protože do ní tato paměť vytekla :)
  • Práce s uvolněnou pamětí - Může se nám stát, že nějakou paměť uvolníme a poté se na tuto adresu pokusíme znovu něco zapsat. V tu chvíli však zapisujeme opět na paměť, která nám nepatří, následky viz. minulý bod. Proto je dobré uložit do ukazatele po uvolnění jeho paměti hodnotu NULL, abychom se této chybě vyvarovali.

V příští lekci, Aritmetika ukazatelů v jazyce C, se naučíme tzv. pointerovou aritmetiku a zjistíme, že ukazatele v jazyce C jsou polím ještě podobnější, než jsme si mysleli.


 

Stáhnout

Staženo 102x (31.81 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
10 hlasů
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Předchozí článek
Úvod do ukazatelů (pointerů) v jazyce C
Všechny články v sekci
Dynamická práce s pamětí v jazyce C
Miniatura
Následující článek
Aritmetika ukazatelů v jazyce C
Aktivity (5)

 

 

Komentáře
Zobrazit starší komentáře (14)

Avatar
David Novák
Redaktor
Avatar
David Novák:7.12.2015 11:52

Jo a u toho -pedantic je ještě velmi vhodné uvést konkrétní standard, jinak tě ti to bude nutit výchozí, což bude asi iso 89..

gcc -std=c11 -pedantic // varuje, když použiješ něco, co není v C11 standardu
Odpovědět 7.12.2015 11:52
Chyba je mezi klávesnicí a židlí.
Avatar
Taskkill
Redaktor
Avatar
Taskkill:7.12.2015 12:47

urcite jsem myslel -g :D jen jsem nepremyslel diky za doplneni ...

 
Odpovědět  +1 7.12.2015 12:47
Avatar
Lukáš Hypša:6.1.2018 18:35

Když zadám jako počet intů 9, vypíše se pouze RUN_SUCCESFUL, při 10 se vypíšou i čísla v cyklu, při 11 se vypíše pouze RUN FAILED. Neví někdo?

int pocet = 10; // pro 9, 10 a 11 se program chová odlišně
int *p_cisla = (int *) malloc(sizeof(int) * pocet);
for(int i = 0; i < pocet; i++)
{
*p_cisla = i;
printf("%d ", *p_cisla);
p_cisla++;
}
free(p_cisla);
p_cisla = NULL;
return (EXIT_SUCCESS);

Odpovědět 6.1.2018 18:35
Jsem lama co se roky snaží naučit napsat aspoň pár řádků a furt mu to nejde...
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Lukáš Hypša
DarkCoder:6.1.2018 21:51

Vytvořený ukazatel na začátek pole je třeba považovat za konstantu a není dovoleno jej měnit. Pro přístup a výpis prvků dynamického pole ukazuje následující kód:

int pocet = 10;
int *p_cisla = (int *)malloc(sizeof(int) * pocet);
for (int i = 0; i < pocet; i++) {
        *(p_cisla + i) = i;
        printf("%d ", *(p_cisla + i));
}
free(p_cisla);
p_cisla = NULL;
Odpovědět  +2 6.1.2018 21:51
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:30. srpna 12:26

prave jsem z prdele vyzkousel:

#include <stdio.h>
 1 #include <stdlib.h>
 2
 3 int main (void) {
 4
 5    int *pi;
 6     for(int i =0; i<999999999999 ; i++)
 7        pi = (int*) malloc(sizeof(int)*100);
 8
 9       printf("sem tu\n");
10
11    return 0;
12 }
13
  • pocitac se mi zaseknul ze vubec nereagoval, tak sem ho manualne restartoval, pri nabootovani se to cele procistilo a uz v cajku, ale ale to celkem prdel toto delat, kdyz vim ze OS se postara, aby se vse vycistilo :D
Editováno 30. srpna 12:27
 
Odpovědět 30. srpna 12:26
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
Avatar
Odpovídá na DarkCoder
Patrik Pastor:30. srpna 12:32

ale vubec to neni tak jak @David_Capka napsal :

Nejhorší situace je, když paměť zapomeneme uvolnit v nějakém cyklu. Paměť nám při této chybě samozřejmě za nějakou dobu dojde, aplikace spadne a uživatel přijde o data a zaplatí raději konkurenci, aby mu prodala funkční aplikaci

Nic takoveho se nestalo, pamet sice dosla, ale ja jsem o zadne data neprisel a operacni system se o vse postaral. Chyba se tedy muze stat (neumyslne), ale pochybuji, ze by nekdo kvuli tomu se za konkurenci, aby mu prodala funkcni aplikaci - to mi prijde smesne.

 
Odpovědět 30. srpna 12:32
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:30. srpna 12:59

Ve své aplikaci s žádnými daty nepracuješ. Skutečné aplikace si načítají potřebná data ze souborů do paměti, kde se s nimi z důvodu rychlosti pracuje. Pokud jsou tato modifikovaná data stále pouze v paměti, lze o ně jednoduše přijít. Pokud aplikace spadne ať už z důvodu nedostatku paměti, nesprávné funkčnosti, či jiného důvodu, o veškerá neuložená data se přijde. Jistě si pak dokážeš představit jak asi spokojený uživatel poté bude, když aplikace nebude stabilní. Ta ztracená data už nezískáš a to rozhodně není úsměvné.

Odpovědět 30. srpna 12:59
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:30. srpna 13:22

tomu rozumim, ale proc se mi potom nic nestalo? Sebral jsem tolik pameti (alokoval si), ze pocitac nebyl schopny reagovat, presto jsem o zadne data neprisel. O jake situaci tedy mluvis? Ja myslel ze to co jsem udelal be me "melo" o data pripravit

 
Odpovědět 30. srpna 13:22
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Patrik Pastor
DarkCoder:30. srpna 17:58

Tím bych si nebyl tak jist. Na pozadí Ti běželo X procesů, které prováděly nějaké operace a tyto operace nemusely být dokončeny a nezapsaly tak výsledek na disk. Jak moc závažné to je nelze říci. Mluvím o tom, kdy aplikace načte data ze souboru, provede na nich operaci, ale výsledek už se nezapíše zpět na disk neboť došlo k havárii systému.

Můžeš si to představit tak, že pokračuješ v psaní dokumentu ve Wordu a počítač Ti spadne. Když se pokusíš soubor otevřít, zjistíš, že si vlastně nic nezpracoval, protože žádná změny se Ti neuložily. Vše bylo pouze v paměti, nikoli však zaznamenané na disku.

Odpovědět 30. srpna 17:58
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovídá na DarkCoder
Patrik Pastor:30. srpna 18:28

ok chapu, je to asi riziko pro jiz vetsi aplikace. Ale co se tyka me, tak sem videl pri bootovani v terminalu, jakk bliko 5 radku (klasicky treba jen jeden) - takze tohle jsem predpokladal ze to OS procistil, nebo se z toho "vzpamatoval".

Jak moc zavazne to je nelze rict.

Nijak sem to od te doby nepocitil, vsechno bezi v normalu, takze je to tak jak si mylism - OS se uz procistil. Pravdepodobne to je asi na Ubuntu od nejake X verze, aby se OS "zpravil" po overrun utokum (to mi prijde logicke), ale jestli OS opravdu ma nejake takova opatreni, fakt nevim

 
Odpovědět 30. srpna 18:28
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 24. Zobrazit vše