12.5 Zarządzanie pamięcią w C

Operatory newdelete są charakterystyczne dla języka C++. W C podobne zadania realizowane są za pomocą innych funkcji — aby z nich korzystać, należy dołączyć plik nagłówkowy cstdlib. Ponieważ stosuje je się bardzo często w istniejącym kodzie, omówimy je tu pokrótce. Nawet pisząc w C++ używa się czasem funkcji z C, gdyż bywają efektywniejsze, choć są często bardziej niebezpieczne i trudniejsze w programowaniu.

Funkcja malloc (ang. memory allocation) przydziela obszar w pamięci wolnej. Jej prototyp ma postać

       void* malloc(size_t size);

Typ size_t jest aliasem typu całościowego bez znaku, który może zależeć od implementacji (zwykle jest tożsamy z  unsigned long). Funkcja malloc alokuje size bajtów pamięci dynamicznej i zwraca wskaźnik do początku zarezerwowanego obszaru jako wskaźnik typu void*. Nie ma tu rozróżnienia między alokowaniem pamięci na pojedynczy obiekt i na tablicę; zawsze musimy podać liczbę bajtów. Funkcja malloc jest rzeczywiście funkcją, wywołuje się ją zatem podając argument w nawiasach okrągłych. Zwraca surowy adres w pamięci, bez żadnej informacji o typie obiektów, jakie będą tam wpisane. A zatem przed przypisaniem tego adresu do zmiennej wskaźnikowej pewnego typu należy dokonać odpowiedniej konwersji, czego nie musieliśmy robić w przypadku operatora new. Na przykład
      1.      int* k = (int*) malloc(sizeof(int));
      2.      *k = 5;
      3.      // ...
      4.      int size;
      5.      cin >> size;
      6.      int* m = (int*) malloc(size*sizeof(int));
      7.      for (int i = 0; i < size; ++i)
      8.          m[i] = 2*i;
Zauważmy, że nawet alokując pamięć na pojedynczą zmienną w linii pierwszej tego przykładu musieliśmy jawnie podać liczbę potrzebnych bajtów. W przypadku niepowodzenia funkcja malloc zwraca wskaźnik zerowy(NULL; w standardzie C++14 jest to raczej nullptr). Przydzielona pamięć nie jest w żaden sposób inicjowana.

Za pomocą innej funkcji, calloc, możemy zaalokować obszar pamięci i od razu zainicjować go zerami. Prototyp ma postać

       void* calloc(size_t count, size_t size);
Parametr count oznacza liczbę elementów, jakie alokowany obszar ma pomieścić, a  size rozmiar (w bajtach) jednego elementu. Zatem, aby zaalokować tablicę liczb całkowitych i zainicjować ją zerami, można napisać
       int dim;
       cin >> dim;
       int* tab = (int*) calloc(dim,sizeof(int));
Zauważmy, że wciąż nie ma tu informacji o typie elementów — zwracany jest wskaźnik typu void*.

Istnieje również funkcja realloc zmieniająca rozmiar bloku pamięci dynamicznej wcześniej zaalokowanego:

       void* realloc(void* ptr, size_t size);
której pierwszym argumentem jest wskaźnik zwrócony wcześniej przez malloc, calloc lub realloc, a  size jest nowym rozmiarem. Jeśli rozmiar size jest większy niż ten który został podany przy wywołaniu funkcji malloc, to zostanie zarezerwowany nowy obszar o zwiększonym rozmiarze, a zawartość poprzedniego bloku pamięci zostanie do niego skopiowana, po czym stary obszar zostanie zwolniony. Funkcja zawsze zwraca adres nowego obszaru, który jednak może pokrywać się ze starym, jeśli size wcale nie jest większe od dotychczasowego rozmiaru.

W przypadku niepowodzenia realloc zwraca wskaźnik zerowy (NULL, nullptr), a dotychczasowy blok pamięci pozostaje nienaruszony.

W tradycyjnym C do zwalniania pamięci dynamicznej służy funkcja free

       void free(void* ptr);
Wartością argumentu ptr musi być adres zwrócony wcześniej przez funkcję malloc, calloc lub realloc podczas alokacji (lub realokacji).

Obszar pamięci zaalokowany za pomocą operatora new (new[]) należy zwalniać za pomocą delete (delete[]). Jeśli do przydzielenia pamięci użyto funkcji malloc, calloc lub realloc, to zwolnić ją trzeba za pomocą funkcji free.

Podobnie jak nie należy mieszać newdelete tablicowych z nietablicowymi, nie wolno też mieszać funkcji przydzielających pamięc z C z funkcjami zwalniającymi pamięć z C++ i odwrotnie.

T.R. Werner, 25 lutego 2017; 22:31