12. Dinamička alokacija memorije

Podeli svoje utiske o poglavlju na anketi https://docs.google.com/forms/d/1v87L2JkRzXnvglKgZtM-rPJjTuzB-93z9LHasXQMbA0

Preduslovi za rad:

Poznavanje funkcija
Poznavanje nizova
Poznavanje pokazivača

Dinamičku alokaciju koristimo kada ne znamo unapred, pre pokretanja programa, koliko nam je memorije potrebno za rad. Do sada smo u radu sa nizovima uvek unapred određivali veličinu niza. Kada ovako jednom odredimo veličinu niza, ne možemo je kasnije menjati. Ta memorija nam može postati mala u nekom trenutku. Problem nedovoljne memorije rešavamo tako što je zauzimamo u toku izvršavanja programa, tj. alociramo je dinamički. Dinamički alociranu memoriju je potrebno uvek nakon korišćenja osloboditi.

Funkcije za dinamičku alokaciju memorije su malloc, calloc, realloc i free. Sve funkcije se nalaze u biblioteci stdlib.h, pa je potrebno uključiti je kako bi ih koristili. Funkcije malloc i calloc služe za zauzimanje memorije, a funkcija realloc za proširenje/smanjenje prethodno dinamički zauzete memorije. Ove tri funkcije vraćaju adresu početka memorijskog bloka ukoliko je memorija uspešno zauzeta ili NULL vrednost ukoliko se memorija ne može zauzeti. Imajući ovo u vidu, uvek je potrebno proveriti da li su ove funkcije uspešno zauzele memoriju i izaći iz programa ako nisu. Funkcija free služi za oslobađanje dinamički zauzete memorije.

12.1. Malloc

Funkcija malloc se koristi za dinamičku alokaciju memorijskog bloka određene veličine. Funkcija vraća generički pokazivač tipa void *, koji se može pretvoriti u bilo koji pokazivač. Ne inicijalizuje zauzetu memoriju na početnu vrednost. Osnovna forma funkcije je:

void *malloc(size_t size);

gde size_t size predstavlja veličinu memorije koju će malloc pokušati da zauzme.

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4int main()
 5{
 6    int *a, n;
 7
 8    do
 9    {
10        printf("Unesite duzinu niza: ");
11        scanf("%d", &n);
12
13    } while(n <= 0);
14
15    a = (int *) malloc(n * sizeof(int));
16
17    if (a == NULL)
18    {
19        printf("Memorija se ne moze zauzeti.\n");
20        exit(EXIT_FAILURE);
21    }
22
23    for(int i = 0; i < n; i++)
24    {
25        printf("a[%d] = ", i);
26        scanf("%d", &a[i]);
27    }
28
29    printf("Uneti niz je:\n");
30    for(int i = 0; i < n; i++)
31    {
32        printf("a[%d] = %d\n", i, a[i]);
33    }
34
35    free(a);
36    return 0;
37}

U primeru iznad prvo unosimo dužinu niza, a zatim zauzimamo memoriju za tačno toliko članova niza korišćenjem funkcije malloc. Za određivanje veličine memorijskog bloka koji nam je potreban, koristimo operator sizeof. Ovaj operator može se koristiti sa tipovima podataka ili sa promenljivama, tako da je jedan od načina da mu prosledimo tip podatka. Na primer, sizeof(int) nam daje veličinu jednog integer-a u bajtovima, dok sa izrazom n * sizeof(int) dobijamo ukupnu veličinu memorije potrebnu za ceo niz.

Nakon što pokušamo da zauzmemo memoriju, potrebno je da proverimo da li je to uspešno učinjeno. Ako malloc vrati NULL, to znači da je zauzimanje memorije neuspešno i u tom slučaju treba da izađemo iz programa. Ako je memorija uspešno zauzeta, unosimo članove niza, a zatim ih ispisujemo.

Kada nam više nije potrebna, ili kada smo završili sve što smo planirali u vezi sa dinamičkom memorijom, oslobađamo zauzetu memoriju korišćenjem funkcije free. Iako u primerima često oslobađamo memoriju na kraju programa, u stvarnim aplikacijama, posebno onima koje rade u realnom vremenu ili u dugoročnim procesima (npr. serveri), važno je da oslobađamo memoriju čim nam više nije potrebna, kako bismo izbegli curenje memorije.

Ispis ovog programa je:

Unesite duzinu niza: 3
a[0] = 7
a[1] = 2
a[2] = 9
Uneti niz je:
a[0] = 7
a[1] = 2
a[2] = 9

12.2. Free

Funkcija free služi za oslobađanje dinamički zauzete memorije. Memorija se zauzima nekom od funkcija za dinamičku alokaciju, koje vraćaju pokazivač na zauzetu memoriju. Ova funkcija očekuje pokazivač na tu memoriju kako bi je oslobodila. Osnovna forma funkcije je:

void free(void *ptr);

12.3. Calloc

Funkcija calloc se koristi za dinamičku alokaciju memorijskog bloka određene veličine. Blokovi memorije koje alocira funkcija calloc su sukcesivni, što znači da su raspoređeni uzastopno u memoriji. Ovo je bitna karakteristika, jer omogućava efikasno korišćenje memorije i lakše upravljanje sa skupovima podataka koji se koriste u kontinuitetu. Funkcija vraća generički pokazivač tipa void *, koji se može pretvoriti u bilo koji pokazivač. Ukoliko dođe do greške u alokaciji, funkcija će vratiti NULL vrednost. Za razliku od funkcije malloc, koja samo alocira memoriju bez inicijalizacije, calloc automatski postavlja sve bajtove u dodeljenoj memoriji na nulu. To znači da će svaka pozicija u memoriji biti postavljena na 0 (ili NULL u slučaju pointera). Osnovna forma funkcije je:

void *calloc(int n, size_t size);

gde size_t size predstavlja veličinu jednog memorijskog bloka, a n broj blokova koje će calloc pokušati da zauzme.

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4int main()
 5{
 6    int *a, n;
 7
 8    do
 9    {
10        printf("Unesite duzinu niza: ");
11        scanf("%d", &n);
12
13    } while(n <= 0);
14
15    a = (int *) calloc(n, sizeof(int));
16
17    if (a == NULL)
18    {
19        printf("Memorija se ne moze zauzeti.\n");
20        exit(EXIT_FAILURE);
21    }
22
23    for(int i = 0; i < n; i++)
24    {
25        printf("a[%d] = ", i);
26        scanf("%d", &a[i]);
27    }
28
29    printf("Uneti niz je:\n");
30    for(int i = 0; i < n; i++)
31    {
32        printf("a[%d] = %d\n", i, a[i]);
33    }
34
35    free(a);
36    return 0;
37}

U primeru iznad je sve isto kao u primeru sa malloc funkcijom, uz razliku da je memorija ovde zauzeta korišćenjem calloc funkcije. Opet je potrebno proveriti da li je memorija uspešno zauzeta, kao i osloboditi je na kraju programa.

Ispis ovog programa je:

Unesite duzinu niza: 3
a[0] = 1
a[1] = 2
a[2] = 3
Uneti niz je:
a[0] = 1
a[1] = 2
a[2] = 3

12.4. Realloc

Funkcija realloc koristi se za promenu veličine prethodno alociranog bloka memorije. Memorija se prethodno mora zauzeti korišćenjem funkcija malloc ili calloc. Realokacijom memorije se zadržavaju prethodne vrednosti i ne inicijalizuje se memorija na početnu vrednost. Ukoliko je potrebno proširiti alocirani blok memorije (povećate njegovu veličinu), realloc će to i pokušati. Ako nije moguće proširiti memoriju na postojećoj lokaciji (zbog fragmentacije ili drugih razloga), realloc može alocirati novi blok u nekoj drugoj lokaciji i prekopirati sadržaj iz stare memorije u novu. Ukoliko je potrebno smanjiti veličinu memorijskog bloka, realloc jednostavno seče blok i oslobađa višak memorije, čuvajući samo prvih new_size bajtova. Ako je alokacija uspešna, realloc vraća generički pokazivač na prošireni ili skraćeni memorijski blok tipa void *. Ovaj pokazivač može biti isti kao prethodni ili može biti nova adresa u slučaju premeštanja bloka. Ukoliko dođe do greške i nije moguće alocirati memoriju, realloc vraća NULL, a originalni blok memorije ostaje nepromenjen. Osnovna forma funkcije je:

void *realloc(void *ptr, size_t new_size);

gde size_t new_size predstavlja novu veličinu memorije koju je potrebno zauzeti, a void *ptr pokazivač na prethodno zauzetu memoriju koju je potrebno smanjiti ili povećati.

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4int main()
 5{
 6    int *a, n;
 7
 8    do
 9    {
10        printf("Unesite duzinu niza: ");
11        scanf("%d", &n);
12
13    } while(n <= 0);
14
15    a = (int *) malloc(n * sizeof(int));
16
17    if (a == NULL)
18    {
19        printf("Memorija se ne moze zauzeti.\n");
20        exit(EXIT_FAILURE);
21    }
22
23    for(int i = 0; i < n; i++)
24    {
25        printf("a[%d] = ", i);
26        scanf("%d", &a[i]);
27    }
28
29    int m;
30
31    do
32    {
33        printf("Unesite novu duzinu niza: ");
34        scanf("%d", &m);
35
36    } while(m <= 0);
37
38    // Pokušaj da se poveća veličina niza koristeći realloc
39    int *tmp = (int *) realloc(a, m * sizeof(int));
40
41    // Provera da li realloc nije uspeo
42    if (tmp == NULL)
43    {
44        // Greška pri alokaciji, originalni blok je i dalje validan
45        free(a); // Oslobađanje prethodno alocirane memorije
46        printf("Memorija se ne moze zauzeti.\n");
47        exit(EXIT_FAILURE);
48    }
49
50    // Ako realloc uspe, dodeljujemo novi pokazivač
51    a = tmp;
52
53    if (m > n)
54    {
55        for(int i = n; i < m; i++)
56        {
57            printf("a[%d] = ", i);
58            scanf("%d", &a[i]);
59        }
60    }
61
62    printf("Uneti niz je:\n");
63    for(int i = 0; i < m; i++)
64    {
65        printf("a[%d] = %d\n", i, a[i]);
66    }
67
68    free(a);
69    return 0;
70}

U primeru iznad unosimo prvo dužinu niza, nakon čega zauzimamo memoriju za tačno toliko članova niza korišćenjem funkcije malloc. Kada smo pokušali da zauzememo memoriju, potrebno je da proverimo da li je ona uspešno zauzeta. Ukoliko je malloc vratio NULL, izlazimo iz programa, ukoliko nije, unosimo članove niza. Nakon toga, unosimo novu dužinu niza i zauzimamo memoriju za promenjenu dužinu korišćenjem funkcije realloc. Funkciji realloc prosleđujemo pokazivač na niz i m * sizeof(int) gde je m nova dužina niza. Potrebno je da ponovo proverimo da li je memorija uspešno zauzeta ili je funkcija realloc vratila NULL. Ukoliko je memorija zauzeta, proveravamo da li je nova dužina niza veća od stare i u tom slučaju unosimo ostale elemente niza. Za dužinu niza sada uzimamo m, pa će for petlja za ispis svih elemenata niza ići do ove vrednosti. Na kraju programa, oslobađamo dinamički zauzetu memoriju sa funkcijom free.

Ispis ovog programa je:

Unesite duzinu niza: 3
a[0] = 1
a[1] = 2
a[2] = 3
Unesite novu duzinu niza: 5
a[3] = 4
a[4] = 5
Uneti niz je:
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5