| ← ukazatele na funkce | C/C++ | knihovna string.h → |
Každý program lze v zásadě rozdělit na dvě části. Na část datovou, kde jsou uloženy data, se kterými program pracuje (tj. proměnné) a na část programovou, která obsahuje instrukce programu. Ihned po spuštění programu jsou data která ukládáte do proměnných uložena v paměti počítače. Z této paměti lze data číst a lze do ní i zapisovat.
Takto získávaná paměť má své pro i proti. Výhodou je v zásadě rychlost a zaručená dostupnost paměti.Ovšem jsou situace, kdy dopředu nemůžeme vědět, kolik budeme paměti potřebovat. Například budeme chtít program, který načte od uživatele několik čísel a pak je setřídí od nejmenšího do největšího. Jelikož dopředu nevíme, kolik čísel bude uživatel chtít setřídit, můžeme to jenom odhadnout a vytvořit si pro tyto čísla pole dlouhé např. 1000 položek typu float. To už je velké pole, které zvětší velikost programu a zpomalí jeho chod, přičemž se může stát, že uživatel bude využívat v průměru třeba jen 10 položek. Na druhou stranu se také může stát, že bude potřebovat 1001 položek.
Naštěstí nejste odkázáni jen na paměť představovanou proměnnými, které deklarujete ve zdrojovém kódu (taková místa v paměti se nazývají statická). Můžete žádat o paměť pro proměnnou (nebo celá pole proměnných) za chodu programu. Tomu se říká „dynamická alokace paměti“.
I dynamická alokace paměti má své výhody a nevýhody. Výhodou je, že program využívá právě tolik paměti, kolik potřebuje. Navíc získanou paměť můžete během programu uvolnit. Takže když například jednou potřebujete 10 MB dat a podruhé 15 MB dat, nejdříve požádáte o 10 MB, využijete je, potom uvolníte, pak požádáte o těch potřebných 15 MB dat... Takže i když jste potřebovali celkem 25 MB dat, v jeden okamžik jste jich potřebovali maximálně 15. Jistou nevýhodou je zvýšená pracnost s dynamickými strukturami (a časová režie při běhu programu). Proto vždy rozvažte, zda se vyplatí (například pro ukládání řetězce čteného od uživatele programu) vytvářet dynamické data, nebo využít statických polí.
Nejdříve se podívejte na deklarace funkcí malloc() a free() a pak si popíšeme jejich funkce. Obě funkce najdete v hlavičkovém souboru <stdlib.h>. Datový typ size_t, který budeme používat k určování velikosti získávané paměti, je definován též v <stdlib.h>. Je to datový typ definovaný pomocí typedef. Je to celočíselný typ, něco jako unsigned int. Máte zaručeno, že size_t je dostatečně velký na to, aby se v něm mohla uložit adresa paměti Proto používejte size_t a ne int, který nemusí být dostatečně velký na adresování celého rozsahu paměti.
void *malloc(size_t size); void free(void *ptr);
Funkce malloc() má jako jediný argument počet bajtů paměti, které chcete od operačního systému získat. Zajímavá je návratová hodnota, která je deklarována jako ukazatel void *. Asi vás už napadlo, že s dynamicky alokovanou pamětí se bude pracovat pomocí ukazatelů. Novou paměť vám totiž přidělí operační systém a funkce malloc() vrátí ukazatel na začátek paměti, která byla programu operačním systémem přidělena. Samo sebou se může stát, že již není dostatek volné paměti. V takovém případě funkce malloc() vrací hodnotu NULL. Vrácený ukazatel je třeba přetypovat na správný datový typ, který budete chtít v alokovaném datovém poli (nebo jedné proměnné) používat.
Paměť, kterou takto získáte, je souvislá oblast dlouhá size_t bajtů s nedefinovaným obsahem. Jsou v ní tedy náhodné hodnoty bajtů. Pokud použijete funkci malloc() vícekrát, získáte několik souvislých oblastí, ale vzájemně již souvislé být nemusí a také většinou nejsou. Maximální velikost dynamicky alokované paměti je dána jednak fyzickými možnostmi vašeho počítače a také velikostí adresování paměti. U 32 bitových programů je možné adresovat až 4GB, u 16 bitových je to samozřejmě méně a u 64 bitových více.
Jak jsem již v začátku kapitoly předeslal, dynamicky alokovanou (získanou) paměť je možné operačnímu systému vrátit a tím snižovat nároky programu na paměť. To se provádí funkcí free(). Tato funkce má jako argument ukazatel na začátek alokované paměti (tedy hodnotu, kterou vrací funkce malloc(), calloc() a realloc() viz níže). Pokud funkci free() v programu nepoužijete, je alokovaná paměť vrácena operačnímu systému po skončení programu. Přesto by jste měli funkci free() v programu vždy volat.
Pokud program neuvolňuje nepotřebnou paměť, dochází k takzvanému „úniku paměti“ (memory leaks). Déle běžící aplikace si tak říká o stále více a více paměti, až nakonec spotřebuje všechnu, která je dostupná. Takovými problémy často trpí například Mozilla Firefox (ale i mnoho jiných profesionálních aplikací).
Po volání funkce free() již paměť není přidělena programu a pokus o zápis do této paměti by byl po zásluze potrestán sejmutím programu nebo dokonce operačního systému (pokud je tak hloupý a nechá si to líbit).
Následující zdrojový kód ukazuje použití funkcí malloc() a free(). Jde o program, který načte od uživatele číslo určující počet následujících zadávaných čísel, které se poté setřídí. Všimněte si, jak je v programu pomocí NULL kontrolováno, zda byla dynamická paměť skutečně alokována, jak je přetypována návratová hodnota a jak se určuje velikost získávaného datového pole.
1: /*------------------------------------------------*/ 2: /* dynamic1.c */ 4: #include <stdio.h> 5: #include <stdlib.h> 7: /* funkce pro setrideni pole od nejmensiho do nejvetsiho */ 8: void setrid(unsigned int delka, float pole[]) 9: { 10: unsigned int i, j; 11: float pom; 12: if (delka <= 1) 13: return; 15: for (j = 0; j < delka - 1; j++) { 16: i = 0; 17: do { 18: if (pole[i] > pole[i + 1]) { 19: pom = pole[i]; 20: pole[i] = pole[i + 1]; 21: pole[i + 1] = pom; 22: } 23: i++; 24: } while (i < delka - 1); 25: } 26: } 28: int main(void) 29: { 30: unsigned int i; 31: unsigned int *ui; /* ukazatel na dynamicky alokovane cislo, ktere bude urcovat 32: delku pole pro tridene cisla. V tomto pripade by bylo 33: lepsi pouzit statickou promennou (jako unsigned int i); je to 34: tu tak udelano jen pro nazorny priklad */ 35: float *uf; /* ukazatel na dynamicky alokovane pole, do ktereho budou 36: ulozeny tridene cisla */ 39: ui = (int *) malloc(sizeof(int)); 40: if (ui == NULL) { /* jeden mozny zpusob kontroly */ 41: printf("Nedostatek pameti!\n"); 42: return 0; 43: } 45: printf("Zadejte pocet cisel: "); 46: scanf("%u", ui); /* u funkce scanf by se melo kontrolovat, zda skutecne 47: nacetla to, co mela. Pro strucnost prikladu to nedelam */ 49: /* pozadam o tolik bytu, kolik ma typ float krat velikost pole */ 50: if ((uf = (float *) malloc(sizeof(float) * (*ui))) == NULL) 51: /* taky zpusob kontroly :-) */ 52: { 53: printf("Nedostatek pameti!\n"); 54: return 0; 55: } 57: for (i = 0; i < *ui; i++) { 58: printf("Zadejte cislo [%2u]: ", i + 1); 59: scanf("%f", uf + i); /* nebo uf[i] */ 60: } 62: setrid(*ui, uf); 63: for (i = 0; i < *ui; i++) { 64: printf("%5.2f ", uf[i]); /* nebo uf + i */ 65: } 66: printf("\n"); 67: free(ui); 68: free(uf); 69: return 0; 70: } 72: /*------------------------------------------------*/
Výstup z programu:
Zadejte pocet cisel: 5 Zadejte cislo [ 1]: 4 Zadejte cislo [ 2]: 3.5 Zadejte cislo [ 3]: 3.8 Zadejte cislo [ 4]: 2 Zadejte cislo [ 5]: 0.5 0.50 2.00 3.50 3.80 4.00
V následujícím příkladě uvidíte, jak lze dynamicky vytvořit dvourozměrné pole. Jeho velikost bude 100*200*sizeof(int) bajtů. Především si všimněte, jak je paměť uvolňována.
1: /*------------------------------------------------*/ 2: /* dynamic2.c */ 4: #include <stdio.h> 5: #include <stdlib.h> 7: #define X 100 /* pocet radku */ 8: #define Xv1 10 /* vypis radek Xv1 az Xv2 */ 9: #define Xv2 20 11: #define Y 200 /* sloupce */ 12: #define Yv1 50 /* vypis sloupcu Yv1 az Yv2 */ 13: #define Yv2 60 15: int main(void) 16: { 17: int **ui; 18: int a, b; 20: ui = (int **) malloc(X * (sizeof(int *))); 21: if (ui == NULL) 22: return 1; 24: for (a = 0; a < X; a++) { 25: ui[a] = (int *) malloc(Y * (sizeof(int))); 26: if (ui[a] == NULL) 27: return 1; 28: } 30: for (a = 0; a < X; a++) 31: for (b = 0; b < Y; b++) 32: ui[a][b] = a * b; 34: printf(" "); 35: for (b = Yv1; b <= Yv2; b++) 36: printf("%5i ", b); 38: printf("\n"); 39: for (a = Xv1; a <= Xv2; a++) { 40: printf("%3i: ", a); 41: for (b = Yv1; b <= Yv2; b++) 42: printf("%5i ", ui[a][b]); 43: printf("\n"); 44: } 46: /* jak byla pamet alokovana, tak musi byt dealokovana. 47: * Jednoduseji to opravdu nejde. */ 48: for (a = 0; a < X; a++) 49: free(ui[a]); 50: free(ui); 52: return 0; 53: } 55: /*------------------------------------------------*/
Pokud bychom zavolali jen free(ui);, uvolnila by se jen paměť alokovaná prvním zavoláním funkce malloc(). Navíc by jsme tak stratili všechny ukazatele na paměť získávanou v cyklu na řádku 25. To by byla krásná ukázka úniku paměti.
Funkce calloc() je deklarována následovně:
void *calloc(size_t nmemb, size_t size);
Funkce calloc() alokuje paměť pro nmemb položek velikosti size. Velikost položky může být například sizeof(int), počet položek rozměr požadovaného pole. Ovšem častěji se využívá pro vytvoření dynamického pole struktur.
Tato funkce, na rozdíl od funkce malloc(), vyplní alokovanou paměť nulami. Všechno ostatní je stejné jako u funkce malloc() (návratová hodnota, uvolňování pomocí free() atd.). Musíte samozřejmě počítat s nějakou časovou režijí, kterou zabere nulování paměti.
V příkladu si ukážeme, jak lze vytvářet seznam. Seznam obsahuje datové objekty (struktury), které jsou mezi sebou provázány ukazateli. První prvek seznamu ukazuje na druhý, druhý na třetí atd. V našem příkladě bude prvek obsahovat kromě ukazatele na další prvek pole typu char, do kterého budeme ukládat slova načtené od uživatele. Po skončení načítání slova lexikograficky seřadíme. Struktura bude také obsahovat číslo určující kolikáté bylo slovo načteno.
K setřídění použijeme funkci strcmp(), kterou si popíšeme v kapitole věnované standardnímu souboru <string.h>. Tato funkce porovnává lexikograficky dva řetězce a vrací nulu, pokud jsou si rovny!
1: /*------------------------------------------------*/ 2: /* dynamic3.c */ 4: #include <stdio.h> 5: #include <stdlib.h> 7: #define MAXSLOVO 20 8: #define NACTISLOVO "%20s" 10: struct veta { 11: unsigned int poradi; 12: char slovo[MAXSLOVO + 1]; 13: struct veta *dalsi; /* ukazatel na dalsi strukturu */ 14: }; 17: /* pomocna funkce na kopirovani retezce */ 18: void strcopyruj(char *cil, const char *zdroj) 19: { 20: unsigned int i = 0; 21: do { 22: cil[i] = zdroj[i]; 23: } while (zdroj[i++]); 24: } 27: /* funkce vypisujici seznam */ 28: void vypis_veta(struct veta *zacatek) 29: { 30: struct veta *dalsi; 32: dalsi = zacatek; 33: printf("\n"); 34: do { 35: if (strcmp(dalsi->slovo, "")) /* prazdne slova ignoruj */ 36: printf("%3i: %s\n", dalsi->poradi, dalsi->slovo); 37: dalsi = dalsi->dalsi; /* prejdi na dalsi slovo */ 38: } 39: while (dalsi != NULL); 40: } 43: void setrid_seznam(struct veta *zacatek) 44: { 45: int zmena = 0; 46: struct veta *dalsi, pom; 48: dalsi = zacatek; 49: if (dalsi == NULL) 50: return; 52: do { 53: /* dosli jsme na konec a nic se nezmenilo? 54: pak mame setrideno */ 55: if ((dalsi->dalsi == NULL) && (zmena == 0)) 56: break; 57: else if (dalsi->dalsi == NULL) { 58: dalsi = zacatek; 59: zmena = 0; 60: } 61: if (strcmp(dalsi->slovo, dalsi->dalsi->slovo) > 0) { 62: pom = *dalsi; 63: strcopyruj(dalsi->slovo, dalsi->dalsi->slovo); 64: dalsi->poradi = dalsi->dalsi->poradi; 65: strcopyruj(dalsi->dalsi->slovo, pom.slovo); 66: dalsi->dalsi->poradi = pom.poradi; 67: zmena = 1; 68: } 69: dalsi = dalsi->dalsi; 70: } while (1); 71: } 74: /* Nasleduje funkce pro uvolneni pameti. Prvni polozka je staticka, proto se 75: * pomoci free() neuvolnuje a take se musi jeji atribut "dalsi" nastavit 76: * na NULL */ 78: void zabij(struct veta *zacatek) 79: { 80: struct veta *v, *pom; 82: v = zacatek->dalsi; 83: while (v != NULL) { 84: pom = v->dalsi; 85: free(v); 86: v = pom; 87: } 88: zacatek->dalsi = NULL; 89: } 91: int main(void) 92: { 93: unsigned int poradi; 94: int navrat; 95: struct veta prvni, *posledni; 97: printf("Zadavejte slova ne delsi nez %i znaku.\n" 98: "Zadavani vet ukoncete pomoci znaku konec souboru.\n" 99: "(tj. CTRL+D v unixech nebo CTRL+Z v DOSu a Windows)\n\n", 100: MAXSLOVO); 102: poradi = 0; 103: posledni = &prvni; /* "prvni" je prvni prvek ze seznamu. Ukazatel 104: "posledni" ukazuje na posledni prvek v seznamu. */ 105: do { 106: poradi++; 107: navrat = scanf(NACTISLOVO, posledni->slovo); 108: posledni->poradi = poradi; 109: if ((navrat == EOF) || (navrat != 1)) { 110: /* EOF * -> konec vstupu, 111: * navrat !=1 -> nebyl nacten spravne retezec */ 112: /* dalsi prvek v seznamu jiz nebude. seznam ukoncime 113: zarazkou NULL */ 114: posledni->dalsi = NULL; 115: break; 116: } 117: if ((posledni->dalsi = (struct veta *) 118: calloc(1, sizeof(struct veta))) == NULL) { 119: printf("Nedostatek pameti!\n"); 120: exit(1); 121: } 122: posledni = posledni->dalsi; 123: } while (1); 125: setrid_seznam(&prvni); 126: vypis_veta(&prvni); 127: zabij(&prvni); 129: return 0; 130: } 132: /*------------------------------------------------*/
Možný výstup z programu:
Zadavejte slova ne delsi nez 20 znaku. Zadavani vet ukoncete pomoci znaku konec souboru. (tj. CTRL+D v unixech nebo CTRL+Z v DOSu a Windows) Kdyz nejde hora k Moha-Medovi, musi Moha-Med k hore. 1: Kdyz 7: Moha-Med 5: Moha-Medovi, 3: hora 9: hore. 4: k 8: k 6: musi 2: nejde
Lepším příkladem na použití calloc() by byl asi příklad s řídkou maticí (tj matice, která obsahuje spoustu nul). Tam by se ukázala výhoda calloc() oproti malloc().
Funkce realloc() umožňuje změnit velikost alokované paměti. Deklarace funkce je následující:
void *realloc(void *ptr, size_t size);
První argument funkce, ptr je adresa paměti (angl. pointer), která byla alokována pomocí funkcí malloc(), calloc() nebo realloc(). Velikost tohoto paměťového bloku se zvětší (zmenší) na size bajtů. Návratovou hodnotou je ukazatel na „pře alokovanou“ paměť. To ovšem nemusí být stejné místo v paměti jako původní paměť (na kterou ukazoval argument ptr)! Proto pro volání funkce realloc() se již nepokoušejte přistupovat do paměti pomocí hodnoty v ukazateli ptr, ani ji uvolňovat, ale používejte jen paměť na kterou ukazuje hodnota vrácená funkcí realloc().
Pokud by byl argument ptr NULL, pak by bylo volání realloc() obdobné jako malloc(). Pokud by jste velikost pole size nastavili na nulu, bylo by to stejné, jako by jste volali pro ptr funkci free().
Nově alokovaná paměť má nedefinované hodnoty (není vynulována jako u calloc()). Původní část paměti, kterou jsme pře alokovali, zůstává zachována.
Pokud se funkci realloc() nepodaří získat dostatek nové paměti, vrátí ukazatel NULL. Proto by jste neměli v programu používat následující konstrukci:
ukazatel = realloc(ukazatel,SIZE);
Takto by se totiž do ukazatele uložila hodnota NULL a vy by jste přišli o ukazatel na původní alokovanou paměť, která zůstává zachována.
V příkladu bude uživatel zadávat programu požadovanou velkost v megabajtech alokované paměti a program jí bude alokovat.
1: /*------------------------------------------------*/ 2: /* dynamic4.c */ 4: #include <stdio.h> 5: #include <stdlib.h> 7: #define MEGABAJT 1024*1024 9: int main(void) 10: { 11: unsigned long x, a; 12: char *pom, *v = NULL; 14: do { 15: printf("Zadejte pocet megabajtu, 0 pro konec: "); 16: scanf("%lu", &x); 17: pom = (char *) realloc((void *) v, x * MEGABAJT); 18: if ((pom == NULL) && (x)) 19: printf("Nedostatek pameti!!\n"); 20: else { 21: v = pom; 22: /* vyplneni pameti jednickami. 23: * To pocitac trosilinku zamestna :-) */ 24: for (a = 0; a < x * MEGABAJT; a++) { 25: v[a] = '1'; 26: } 27: } 28: } while (x); 30: return 0; 31: } 33: /*------------------------------------------------*/
Pokud spustíte program v Linuxu, můžete na konzoli pomocí příkazu
free sledovat,
jak vám roste a klesá velikost využité paměti počítače
podle toho, kolik si jí zrovna žádáte.
V DOSu a Windows 3.11 bude
1MB souvislé paměti příliš velký požadavek. Pokud překládáte
program tam, žádejte o paměť raději po kilobajtech.
| ← ukazatele na funkce | C/C++ | knihovna string.h → |
