Dynamo pamätlivé

26th Mar 2024

Naučíte sa:

  • Vytvoriť novú premennú alebo pole, aj keď dopredu neviete jej typ.
  • Vytvoriť pole ktorého veľkosť zistíte až po spustení programu.

Dynamicky alokovaná pamäť

Do teraz sa o vyhradenie a uvoľňovanie pamäte staral kompilátor. Stačilo do programu napísať o akú pamäť v akom množstve máme záujem a prekladač sa postaral o ostatné. Automaticky alokovanú pamäť nazývame statická. Nevýhodou je, že veľkosť a typ pamäte musí byť známy už počas doby prekladu.

Statická pamäť je alokované na tzv. zásobníku (stack), časť pamäte, ktorá je pridelená konkrétnemu volaniu funkcie.

Dynamickú pamäť veľkosti 100 bajtov si alokujeme príkazom malloc. Hneď ako pamäť nebudeme potrebovať, uvoľníme ju príkazom free.

void* pole = malloc(100);
free(pole);

V smerníku pole je uložená adresa prvého prvku súvislej oblasti. Tento smerník zatiaľ nie je spojený so žiadnym pamäťovým typom - je to smerník na typ void. Na to, aby sme pamäť nejako využili, je potrebné jej nejaký typ priradiť. Pri zmene dátového typu nie sme ničím obmedzený - môžeme použiť ľubovoľný existujúci typ, ktorý sa zmestí do vyhradeného miesta. Túto pamäť môžeme využiť na uloženie reťazca:

void* pole = malloc(100);
char* retazec = pole;
strcpy(retazec,"Ahoj svet!");
printf("%s\n",retazec);
free(pole);

Príkaz malloc() má podobnú funkciu, ako príkaz na statické vyhradenie pamäte:

char pole[100];

Rozdiel je, že dynamicky vyhradená pamäť sa automaticky neuvoľní, ale zostane prístupná až do vtedy, pokiaľ ju explicitne neuvoľníme pomocou free. Vo vyhradenej pamäti sa nachádzajú nedefinované hodnoty, preto je pole musíme inicializovať na nejakú počiatočnú hodnotu. Príkazom strcpy sme do vyhradeného miesta nakopírovali zadaný reťazec. Klasický operátor priradenia (char* retazec="Ahoj Svet\n";) nebude fungovať podľa očakávania - do smerníka sa priradí adresa staticky alokovanému reťazcu.

Pretypovanie smerníka

Podobným spôsobom si z rovnakej pamäte môžeme vytvoriť aj pole celých čísel.

void* pole = malloc(100);
int* cele_cisla = pole;
cele_cisla[0] = 1;
cele_cisla[1] = 2;
printf("%d %d\n",cele_cisla[0],cele_cisla[1]);
free(pole);

Došlo ale k plytvaniu pamäťou - hoci potrebujeme miesto iba pre dve celočíselné hodnoty, vyhradili sme si miesto až 100 bajtov. Na to aby sme boli o niečo efektívnejší, potrebujeme pri alokácii poznať poznať veľkosť jedného pamäťového miesta a koľko miest potrebujeme. a po použijeme operátor sizeof. Pre skrátenie zápisu vykonáme operáciu pretypovania smerníka typu void* na typ int* hneď.

int* cele_cisla  = (int*)malloc(sizeof(int)* 2);

Takto sme zadali požiadavku operačnému systému pre vyhradenie miesta na dve premenné typu int hneď vedľa seba.

Štruktúry

Príkazom malloc si vieme vyhradiť pamäť pre akýkoľvek dátový typ. Môžeme si dynamicky vyhradiť miesto pre jednu premennú typu int:

int* cele_cislo = malloc(sizeof(int));

V skutočnosti sme si vyhradili miesto až pre dve premenné. Niekde na pamäti (na kope) sa nachádza miesto, kde si uložíme číselnú hodnotu. K tejto hodnote ale nemôžeme pristupovať priamo, ale musíme využiť smerníkovú premennú int* cele_cislo ktoré je alokované staticky a operátor *.

*cele_cislo = 2;

Podobným spôsobom vieme dynamicky alokovať pamäť aj pre zložitejšie dátové typy, napr. štruktúru. Statickým spôsobom by sme to urobili takto:

#include <stdio.h>
struct datum {
    int den;
    int mesiac;
    int rok;
};

int main(){
    struct datum dnes;
    dnes.den = 1;
    dnes.mesiac = 1;
    dnes.rok = 2018;
    printf("Dnes je %d.%d.%d\n",dnes.den,dnes.mesiac,dnes.rok);
    return 0;
}

Dynamický spôsob by bol:

struct datum* dnes = malloc(sizeof(struct datum));
(*dnes).den = 1;
(*dnes).mesiac = 1;
(*dnes).rok = 2018;
printf("Dnes je %d.%d.%d\n",(*dnes).den,(*dnes).mesiac,(*dnes).rok);
free(dnes);

K prístupu k členom dynamicky alokovanej štruktúry musíme využiť operátor * . Operátor () hovorí, že operácia dereferencie sa má aplikovať iba na prvú časť výrazu dnes.den a nie na celý výraz.

Dynamicky alokované štruktúry sa v jazyku C vyskytujú pomerne často a prístup k ich členom pomocou zátvoriek a hviezdičky je pomerne zložitý. Preto z estetických dôvodov vieme nahradiť operátor * pre prístup k členom štruktúry operátorom . Predošlý príklad potom môžeme zapísať takto:

struct datum* dnes = malloc(sizeof(struct datum));
dnes->den = 1;
dnes->mesiac= 1;
dnes->rok = 2018;
printf("Dnes je %d.%d.%d\n",dnes->den,dnes->mesiac, dnes->rok);
free(dnes);

Ak chceme všetky členy štruktúry inicializovať na nulu, môžeme to tak urobiť naraz pomocou funkcie memset:

struct datum* dnes = malloc(sizeof(struct datum));
memset(dnes,0,sizeof(struct datum));
printf("Dnes je %d.%d.%d\n",dnes->den,dnes->mesiac, dnes->rok);
free(dnes);

Pre ešte lenivejších kolegov je možné alokáciu a inicializáciu vykonať v jednom kroku pomocou funkcie calloc:

struct datum* dnes = calloc(sizeof(struct datum));
printf("Dnes je %d.%d.%d\n",dnes->den,dnes->mesiac, dnes->rok);
free(dnes);

Situácia je o niečo komplikovanejšia, ak je členom štruktúry pole alebo reťazec. Musíme dávať pozor, či je reťazec alokovaný staticky alebo dynamicky. Ak si vytvoríme štruktúru, ktorá má reprezentovať údaje o študentovi, môžeme použiť staticky alokovaný reťazec:

struct student {
    char meno[100];
    int rocnik;
};

struct student* daniel = calloc(sizeof(struct student));
daniel->rocnik = 1;
strcpy(daniel->meno,"Daniel");
free(daniel);

Nevýhodou tohto prístupu je, že pamäť nie je využitá veľmi efektívne - každý záznam v databáze študentov zaberie 104 bajtov.

Pamäť môžeme využiť o niečo efektívnejšie, ak využijeme dynamickú alokáciu a meno študenta uložíme do dynamicky alokovaného poľa. Do štruktúry pole si uložíme smerník na začiatok poľa s menom. Daňou za to je o niečo zložitejšia manipulácia so záznamom. Dynamické pole musíme alokovať a uvoľniť osobitne;

struct student {
    char* meno;
    int rocnik;
};

struct student* daniel = calloc(sizeof(struct student));
daniel->rocnik = 1;
malloc(daniel->meno, strlen("Daniel") + 1);
strcpy(daniel->meno,"Daniel");
free(daniel->meno);
free(daniel);

Previous Post Next Post

Dynamo pamätlivé