Materijali/Čas 17

Čas 17 · malloc, calloc, realloc i free

Dinamička memorija: bezbedan životni ciklus bloka

Zauzimamo memoriju tek kada saznamo potrebnu veličinu, proveravamo rezultat, menjamo kapacitet bez gubitka podataka i jasno određujemo ko oslobađa blok.

MAPA ČASA

Od ideje do prvog C programa

1
program
2
podaci
3
promenljiva
4
pseudokod
5
C program

Na kraju je dovoljno da:

  • Zauzmi dinamički niz odgovarajuće veličine i proveri da li je alokacija uspela.
  • Razlikuj malloc i calloc i koristi sizeof nad elementom, a ne nad pokazivačem.
  • Promeni kapacitet bezbednim realloc obrascem sa privremenim pokazivačem.
  • Prepoznaj curenje memorije, pristup posle free i dvostruko oslobađanje.

01 · KORAK

Zatraži, proveri, koristi, oslobodi

Kada se veličina niza sazna tek tokom izvršavanja, program može da zatraži blok memorije funkcijom malloc. Pokazivač čuva početnu adresu bloka, a elementi se zatim koriste kao u običnom nizu: a[0] do a[n-1].

malloc vraća NULL ako zahtev ne može da se ispuni, zato provera mora biti pre prvog pristupa elementu. Sadržaj dobijenog bloka nije inicijalizovan. Na kraju vlasnik poziva free; posle toga se blok više ne čita, ne menja i ne oslobađa ponovo.

int *a = malloc((size_t)n * sizeof *a);
if (a == NULL) return 1;

for (int i = 0; i < n; i++) scanf("%d", &a[i]);
/* obrada niza */
free(a);
a = NULL;
Pronađi ceo životni ciklusU kodu označi trenutak nastanka bloka, prvu dozvoljenu upotrebu, poslednju dozvoljenu upotrebu i trenutak kada blok prestaje da bude naš.
sizeof prati tip elementaIzraz sizeof *a meri jedan element niza. sizeof a meri sam pokazivač i ne govori koliki je blok dobijen funkcijom malloc.

02 · KORAK

Dinamički niz se indeksira kao običan niz

Ako a pokazuje na početak bloka sa n elemenata, a[i] i *(a + i) označavaju isti element. Dozvoljeni indeksi su od 0 do n-1; činjenica da je prostor dobijen funkcijom malloc ne menja pravila indeksiranja.

Pokazivač može privremeno da pokazuje i unutar bloka, na primer p = a + 2. Tada je *p isto što i a[2], ali p[-3] izlazi ispred početka bloka. Program mora da pamti granice zasebno jer ih sam pokazivač ne sadrži.

int *p = a + 2;
printf("%d %d\n", *p, p[1]);
/* isto: a[2] i a[3] */
Indeks, adresa i granicaZa niz 7, -2, 7, 4, 9 odredi vrednosti a[2], *(a+3), *p i p[1], zatim objasni zašto p[-3] nije element tog niza.
Pokazivač ne pamti broj elemenataUz dinamički niz skoro uvek se čuva i n. Ako niz može da raste, odvojeno se čuvaju broj popunjenih elemenata i kapacitet bloka.

03 · KORAK

calloc je koristan kada početne nule imaju značenje

calloc prima broj elemenata i veličinu jednog elementa. Rezervisani bajtovi počinju nulom, pa je dobar izbor za brojače i histograme. Kao i malloc, može da vrati NULL i dobijeni blok mora kasnije da se oslobodi.

malloc ne postavlja početne vrednosti. Ako isti histogram napravimo pomoću malloc, svaki brojač moramo izričito da postavimo na nulu pre prvog uvećavanja.

int *broj = calloc(6, sizeof *broj);
if (broj == NULL) return 1;
for (int i = 0; i < n; i++) broj[ocene[i]]++;
free(broj);
Histogram ocenaZa ocene 2, 5, 3, 5, 4, 5 napiši konačne vrednosti broj[1] do broj[5]. Zatim dodaj inicijalizaciju koja bi bila potrebna kada bi se koristio malloc.
Početne nule ne menjaju vlasništvocalloc štedi petlju za početnu nulizaciju, ali ne zamenjuje free i ne proverava se drugačije od malloc.

04 · KORAK

Neko mora tačno da zna ko oslobađa blok

Funkcija može da napravi blok i vrati njegovu adresu. Pozivalac tada postaje vlasnik: proverava rezultat, koristi blok i na kraju ga oslobađa. Funkcija koja samo obrađuje primljeni niz obično ne poziva free.

Curenje memorije nastaje kada se izgubi poslednja adresa alociranog bloka pre poziva free. Obrnut problem nastaje ako pomoćna funkcija oslobodi blok koji main i dalje koristi: svaki kasniji pristup tada je nevažeći.

int *napravi_nule(int n) {
    if (n <= 0) return NULL;
    int *a = calloc((size_t)n, sizeof *a);
    return a;
}

int *a = napravi_nule(5);
if (a == NULL) return 1;
/* main je vlasnik */
free(a);
Odredi vlasnikaNapiši main koji dobija pet nula, dodaje 3 svakom elementu, prikazuje niz i oslobađa ga. Objasni zašto funkcija koja samo dodaje broj elementima ne sme da pozove free.
Adresa može da se prosledi, vlasništvo ne moraSamo prosleđivanje pokazivača funkciji ne znači da je funkcija preuzela odgovornost za oslobađanje. To pravilo mora biti jasno iz dogovora i koda.

05 · KORAK

Privremeni pokazivač čuva stari blok ako promena ne uspe

realloc menja veličinu postojećeg bloka. Može da zadrži istu adresu ili da premesti sadržaj na novu adresu. Ako uspe, sačuvan je zajednički deo starog i novog bloka; novi elementi posle proširenja nemaju automatske početne vrednosti.

Ako realloc vrati NULL, originalni blok preko starog pokazivača i dalje postoji. Zato rezultat prvo čuvamo u tmp, proveravamo ga i tek onda menjamo vlasnički pokazivač. Direktno a = realloc(a, ...) može da izgubi jedinu adresu starog bloka.

int *tmp = realloc(a, (size_t)novi_kap * sizeof *a);
if (tmp == NULL) {
    free(a);
    return 1;
}
a = tmp;
for (int i = stari_kap; i < novi_kap; i++) a[i] = 0;
Dva ishoda iste naredbeNapiši šta važi za a, tmp i stari sadržaj kada realloc uspe, a šta kada vrati NULL. Zatim napiši bezbedno smanjenje bloka sa osam na pet elemenata.
Broj elemenata nije isto što i kapacitetKapacitet govori koliko mesta postoji, a n koliko ih je stvarno popunjeno. Proširenje menja kapacitet; n raste tek kada se novi element zaista upiše.

06 · KORAK

Ispitna pitanja 20 i 105 traže isti mehanizam

Pitanje 20 glasi: 'Ukratko objasniti koncept dinamičke alokacije memorije i nabrojati funkcije koje se koriste za zauzimanje i oslobađanje memorije.' Pitanje 105 glasi: 'Objasniti mehanizam dinamičkog zauzimanja i oslobađanja memorije u C-u.'

Potpun odgovor povezuje svrhu dinamičke memorije sa funkcijama malloc, calloc, realloc i free. Treba pomenuti proveru prema NULL, mogućnost da realloc premesti blok i zabranu čitanja, upisa ili ponovnog free posle oslobađanja.

int *a = malloc((size_t)n * sizeof *a);
if (a == NULL) return 1;
/* dozvoljen rad sa a[0] ... a[n-1] */
free(a);
a = NULL;
Odgovor bez koda pred sobomPrvo odgovori na pitanje 20 u četiri jasne rečenice. Zatim odgovori na 105 kao životni ciklus: zahtev, provera, upotreba, promena veličine i oslobađanje.
Tri greške koje moraš da prepoznašCurenje memorije je izgubljen alocirani blok. Use-after-free je pristup posle oslobađanja. Double free je ponovni poziv free nad istim već oslobođenim blokom.

ZATVARANJE

Brza provera pre domaćeg

  1. Koja je razlika između algoritma i programa?
  2. Šta je ime, a šta vrednost promenljive?
  3. Kako svojim rečima čitaš x = x + 1?
  4. Koji red prvog programa računa, a koji prikazuje rezultat?