Strukture podataka i algoritmi 1

SPA1 - Vežbe 6

Alociranje memorije

Alociranje memorije se odnosi na proces rezervisanja dela memorije za skladištenje podataka tokom izvršavanja programa. Neoslobađanje memorije nakon završetka korišćenja može dovesti do curenja memorije. Kako bismo mogli da dinamički alociramo memoriju u C-u neophodno je da učitamo biblioteku stdlib.h.

Stack vs heap

Stack (stek) je deo memorije u kojem se čuvaju lokalne promenljive i povratne adrese funkcija tokom izvršavanja programa. Promenljive alocirane na steku automatski se oslobađaju kada funkcija završi izvršavanje. Stek obično ima fiksnu veličinu koja se određuje prilikom pokretanja programa.

Heap (hip) je deo memorije u kojem se dinamički alocira memorija tokom izvršavanja programa. Memorija na heap-u se može koristiti za skladištenje podataka koji su potrebni tokom dužeg vremenskog perioda, kao što su globalne promenljive i dinamički alocirane strukture podataka. Alocirana memorija na hipu se ne oslobađa automatski kada funkcija završi izvršavanje, već mora biti eksplicitno oslobođena pomoću funkcije free(). Heap je obično veći i fleksibilniji od steka, ali upravljanje njime zahteva pažljivo praćenje alocirane i oslobođene memorije kako bi se izbegli curenja memorije i fragmentacija.

free

Potpis funkcije: void free( void *ptr )

Zvanična dokumentacija

Funkciji se kao parametar prosleđuje pokazivač na memoriju koja je dinamički alocirana. Ta memorija se oslobađa i može da je zauzme neki drugi proces ili čak i isti proces, samo za neku drugu namenu. Korišćenje ove funkcije sprečava “curenje” memorije. “Curenje” memorije nastaje kada zauzimamo memoriju koja nam treba, a ne oslobađamo memoriju koja nam treba.

Primer korišćenja: Pogledati primer korišćenje za funkciju malloc.

malloc

Funkcija malloc se koristi za dinamičko alociranje memorije.

Potpis funkcije: void* malloc( size_t size )

size je broj bajtova koji hoćemo da rezervišemo.

Ako imamo neki tip T i želimo da dinamički rezervišemo niz od 10 elemenata to možemo uraditi na sledeći način:

T *ptr = (T*) malloc(10 * sizeof(T));
int *ptr2 = (int*) malloc(10 * sizeof(int));

malloc će u skoro svim slučajevima raditi bez kastovanja.

Ukoliko operativni sistem nađe odgovarajući komad memorije koji može da dodeli procesu, vratiće mu odgovarajuću adresu. Ukoliko nije u mogućnosti da mu dodeli memoriju vratiće mu NULL. NULL je specijalna vrednost koja označava da pokazivač ne pokazuje ni na jednu adresu. NULL se tretira kao false u uslovima.

Zvanična dokumentacija

Primer korišćenja:

#include <stdio.h>   
#include <stdlib.h> 
 
int main() {
    int *p1 = malloc(4*sizeof(int));  // alocira se dovoljno memorije za niz od 4 int-a
    int *p2 = malloc(sizeof(int[4])); // isto, samo što se tip imenuje direktno
    int *p3 = malloc(4*sizeof *p3);   // isto, samo bez ponavljanja imena tipa
 
    if(p1) { // ako OS nije mogao da dodeli memoriju vratiće NULL i neće ući u if
        for(int n=0; n<4; ++n) // popuni niz
            p1[n] = n*n;
        for(int n=0; n<4; ++n) // štampaj ga
            printf("p1[%d] == %d\n", n, p1[n]);
    }
 
    free(p1);
    free(p2);
    free(p3);
}

calloc

Funkcija calloc se koristi za dinamičko alociranje memorije. Prilikom alociranja memorije, svi bajtovi se setuju na nulu.

Potpis funkcije: void* calloc( size_t num, size_t size )

num je broj elemenata koji hoćemo da rezervišemo. size je veličina jednog elementa u bajtovima.

Ako imamo neki tip T i želimo da dinamički rezervišemo niz od 10 elemenata to možemo uraditi na sledeći način:

T *ptr = (T*) calloc(10, sizeof(T));
int *ptr2 = (int*) calloc(10, sizeof(int));

calloc će u skoro svim slučajevima raditi bez kastovanja.

Ukoliko operativni sistem nađe odgovarajući komad memorije koji može da dodeli procesu, vratiće mu odgovarajuću adresu. Ukoliko nije u mogućnosti da mu dodeli memoriju vratiće mu NULL. NULL je specijalna vrednost koja označava da pokazivač ne pokazuje ni na jednu adresu. NULL se tretira kao false u uslovima.

Zvanična dokumentacija

Primer korišćenja:

#include <stdio.h>   
#include <stdlib.h> 
 
int main() {
    int *p1 = calloc(4, sizeof(int));  // alocira se dovoljno memorije za niz od 4 int-a
    int *p2 = calloc(1, sizeof(int[4])); // isto, samo što se tip imenuje direktno
    int *p3 = calloc(4, sizeof *p3);   // isto, samo bez ponavljanja imena tipa
 
    if(p1) { // ako OS nije mogao da dodeli memoriju vratiće NULL i neće ući u if
        for(int n=0; n<4; ++n) // štampaj ga
            printf("p1[%d] == %d\n", n, p1[n]);
    }
 
    free(p1);
    free(p2);
    free(p3);
}

realloc

Funkcija realloc se koristi za dinamičko alociranje memorije. Ova fukcija služi za proširivanje već alociranog prostora.

Ako u novoj memoriji ima dovoljno slobodnog prostora odmah iza originalnog bloka, funkcija jednostavno proširi blok memorije i vrati pokazivač na početak novog bloka.

Ukoliko nema dovoljno slobodnog prostora odmah iza originalnog bloka, funkcija može alocirati novi blok na drugoj lokaciji, kopirati sadržaj originalnog bloka u novi blok, a zatim osloboditi originalni blok. U ovom slučaju, funkcija vraća pokazivač na početak novog bloka.

Ako nema dovoljno slobodne memorije, stara memorija neće biti oslobođena, a funkcija će vratiti NULL.

Potpis funkcije: void *realloc( void *ptr, size_t new_size )

ptr je pokazivač na memoriju koja je već rezervisana. size je broj bajtova koji hoćemo da rezervišemo.

Ako imamo neki tip T i želimo da dinamički proširimo niz koji je već alociran i ima 10 elemenata, tako da nakon proširivanja ima 100 elemenata, to možemo uraditi na sledeći način:

T *ptr1 = (T*) malloc(10 * sizeof(T));
T *ptr2 = (T*) realloc(ptr1, 100 * sizeof(T));

int *ptr11 = (int*) malloc(10 * sizeof(int));
int *ptr22 = (int*) realloc(ptr11, 100 * sizeof(int));

realloc će u skoro svim slučajevima raditi bez kastovanja.

Zvanična dokumentacija

Pokazivač na niz

Prilikom statičke alokacije matrice, rezerviše se veliki komad memorije. Svi vrste matrice se nalaze jedna pored druge.

#include <stdio.h>
int main() {
    int i, j;

    int a[3][3] = {
        { 0, 1, 2 },
        { 10, 11, 12 },
        { 20, 21, 22 }};
    
    int (*pa)[3]; // int *pa[3]; -> pogresno!!!
    pa = a; // možemo da ga koristimo kao da pristupamo matrici a
    
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++)
            printf("%5d", pa[i][j]);
        
        printf("\n");
    }

    printf("%p\n", pa);
    printf("%p\n", pa[0]);
    printf("%p\n", pa[1]);

    return 0;
}

Kada imamo pokazivač na niz integer-a koji ima 3 člana, pa[1] (ili *(pa + 1)) će biti pokazivač na vrstu sa indeksom 1. Slično kao kod pokazivača na int, pa + 1 će zapravo uvećati adresu za 12 (3 int * 4 byte).

Niz pokazivača

#include <stdio.h>
int main() {
    int i, j;

    int a[3][3] = {
        { 0, 1, 2 },
        { 10, 11, 12 },
        { 20, 21, 22 }};
    
    int *pa[3];

    // umesto pa = 1 kao u prethodnom primeru, svakom članu niza pa moramo da kažemo na koji niz pokazuje

    for (i = 0; i < 3; i++)
        pa[i] = a[i];
    
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++)
            printf("%5d", pa[i][j]); // možemo da ga koristimo kao da pristupamo matrici a
        
        printf("\n");
    }

    printf("%p\n", pa);
    printf("%p\n", pa[0]);
    printf("%p\n", pa[1]);

    return 0;
}

Nizove pokazivača ćete koristiti sigurno u radu sa matricom. Pokazivač na niz najverovatnije nećete koristiti, ali je potrebno znati šta je i koja je razlika između niza pokazivača i pokazivača na niz.