Alocare dinamică

Cu toții știm să alocăm memorie static (de ex: int v[100]), unii poate ați mai lucrat și cu STL (de ex: vector<int> v), dar v-ați întrebat vreodată ce face STL în spate? În acest capitol vom începe să răspundem la întrebare.

Memorie

Atunci când declarăm variabile, avem câteva opțiuni pentru unde să fie mai exact memoria alocată.

TODO desen memorie si toate tipurile de memorie Memorie pe heap, stivă, globală, constantă

C style

Pentru a aloca memorie dinamic în C, există câteva funcții pe care ne bazăm.

malloc

void *malloc(std::size_t size) ia ca argument un întreg size și întoarce un pointer la începutul unui bloc contiguu de memorie de size bytes. Întoarce nullptr dacă eșuează.

malloc e cel mai de bază mod de a aloca memorie pe heap în limbajul C. Dacă vrem să alocăm un vector de 100 de numere întregi și să îl folosim ca pe un array, putem aborda astfel:

uint32_t *v = (uint32_t *) malloc(100 * sizeof(uint32_t));
if (v == nullptr) {
    std::cerr << "Failed to allocate";
}

Acest array poate fi folosit mai departe cum sunteți deja obișnuiți cu un array alocat static:

for (int i = 0; i < 100; ++i) {
    v[i] = i;
}
calloc

void *calloc(size_t num, std::size_t size) ia ca argument întregii num și size și întoarce un pointer la începutul unui bloc contiguu de memorie de size * num bytes inițializat pe 0.

Un exemplu similar cu cel de la malloc de mai sus:

uint32_t *v = (uint32_t *) calloc(100, sizeof(uint32_t));

va rezulta într-un array de 100 de întregi, toți setați pe 0.

free

Atunci când alocăm memorie dinamic, este responsabilitatea noastră să o și eliberăm. Dacă omitem să facem asta, riscăm să consumăm din ce în ce mai multă memorie cât timp rulează programul fără a o folosi la ceva, ocupând inutil RAMul utilizatorului și uneori chiar cauzând probleme mai grave. De exemplu, când crasheaza un joc, adesea este din cauza unor memory leakuri (memorie alocată care nu mai poate fi accesată) pentru că a ajuns să consume mai multă memorie decât îi poate oferi sistemul de operare.

void free(void *ptr) ia ca argument pointer către începutul unui bloc de memorie pe care îl va elibera.

realloc

void *realloc(void *ptr, std::size_t new_size) ia ca argument un pointer către începutul unui bloc de memorie pe care îl va muta și dimensiunea pe care trebuie să o aibă noul bloc. Întoarce adresa de memorie de la începutul noului bloc de memorie sau nullptr dacă eșuează. În cazul în care eșuează, blocul original de memorie este păstrat, altfel este eliberat.

Având acces la realloc, putem aloca puțină memorie pentru o structură de date inițial, după care o putem realoca într-o zonă mai mare atunci când este nevoie. De exemplu, vectorii din STL își dublează dimensiunea de fiecare dată când se apelează un push_back care nu ar mai putea fi efectuat în memoria deja existentă.

memset

void* memset(void* dest, int ch, std::size_t count) ia ca argument o adresă de memorie, o valoare ch și un număr de bytes și setează count bytes începând cu adresa dest pe ultimul byte al valorii ch.

De exemplu, pentru a seta un array static v de numere naturale pe 1061109567, putem apela:

memset(v, 0x3f, sizeof(v));

Atenție! Pentru uint32_t v[100], sizeof(v) întoarce 100 * sizeof(uint32_t) = 400. Pentru uint32_t *v = (uint32_t *) malloc(100 * sizeof(uint32_t)), sizeof(v) întoarce dimensiunea unui pointer pe platforma pe care este rulat codul (cel mai probabil 4 sau 8).

Exerciții

  1. Se citesc până la EOF numere între 1 și 1000000 din fișierul cramschool.in. Afișați la consolă numărul de numere și numerele fără să citiți de mai multe ori din fișier. Exemplu Input

    5 8 7 1 231 5343 11 112 998 4
    

    Output

    10
    5 8 7 1 231 5343 11 112 998 4
    
  2. Se dă n număr natural care se poate reprezenta pe 32 de biți fără semn. Fără a declara tablouri statice sau cu STL, creați o matrice bidimensională mat de n linii și n coloane astfel încât următoarea secvență de cod să compileze și să ruleze cu outputul dat: Exemplu Cod

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            mat[i][j] = i + 1;
        }
    }
    
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            std::cout << mat[i][j] << ' ';
        }
        std::cout << '\n';
    }
    

    Input

    5
    

    Output

    1 1 1 1 1
    2 2 2 2 2
    3 3 3 3 3
    4 4 4 4 4
    5 5 5 5 5