Każda tablica z dotychczasowych przykładów miała rozmiar zaszyty w czasie kompilacji: int scores[10]; rezerwuje dziesięć miejsc niezależnie od tego, czy potrzebujesz trzech, czy trzystu. To działa, dopóki rozmiar nie jest znany dopiero w czasie działania programu: ile linii ma plik, ile rekordów wprowadzi użytkownik. Pamięć dynamiczna rozwiązuje ten problem: prosisz o tyle bajtów, ile potrzebujesz, wtedy, gdy ich potrzebujesz, i oddajesz je po zakończeniu pracy.
Narzędzia są w <stdlib.h>:
#include <stdlib.h>
Po co istnieje sterta
C daje twojemu programowi dwa główne obszary pamięci. Zmienne lokalne żyją w pamięci automatycznej (na stosie): kompilator ustala ich rozmiar, a znikają w chwili, gdy ich funkcja zwraca wartość. Alokacje dynamiczne żyją na stercie: to ty decydujesz o rozmiarze w czasie działania, a blok żyje, dopóki go jawnie nie zwolnisz.
Ta druga właściwość to prawdziwy powód, by używać malloc. Funkcja może zaalokować blok, zwrócić wskaźnik, a pamięć nadal jest ważna u wywołującego, czego lokalna tablica nigdy nie potrafi. Oba obszary są szczegółowo porównane w artykule o stosie i stercie.
malloc: prośba o bajty
malloc przyjmuje liczbę bajtów i zwraca void * wskazujący na tyle bajtów niezainicjalizowanej pamięci albo NULL, jeśli żądanie się nie powiedzie.
Cztery rzeczy w tym krótkim programie to cała dyscyplina:
- Wyrażenie rozmiaru to
n * sizeof *scores. Czytaj je jako "n sztuk tego, na co wskazujescores". - Wynik jest sprawdzany względem
NULLprzed jakimkolwiek użyciem. - Bloku używa się dokładnie jak tablicy:
scores[i]działa, bo indeksowanie to arytmetyka wskaźników. - Jest zwalniany raz, a wskaźnik jest potem ustawiany na
NULL.
Zwróć uwagę, że zwracana wartość nie jest rzutowana. W C void * automatycznie zamienia się na dowolny wskaźnik do obiektu, a zapis (int *)malloc(...) dodaje szumu i ukrywa brakujące dołączenie <stdlib.h>. (C++ wymaga rzutowania; C nie.)
Idiom z sizeof
Dlaczego sizeof *scores, a nie sizeof(int)? Bo nie może się zdezaktualizować.
int *a = malloc(n * sizeof *a); /* n intów, jakikolwiek jest tu int */
long *b = malloc(n * sizeof *b); /* ten sam kształt linii, właściwy rozmiar */
/* krucha forma */
long *c = malloc(n * sizeof(int)); /* kompiluje się, alokuje za mało */
Ostatnia linia to prawdziwy błąd, przed którym nie ostrzega żaden kompilator: c to long *, alokacja ma rozmiar liczony dla int, a każdy zapis poza pierwszą połową wychodzi za koniec bloku. Z sizeof *c rozmiar automatycznie podąża za deklaracją.
sizeof *p nie dereferencjonuje p: sizeof jest obliczany w czasie kompilacji na podstawie samego typu, więc jest bezpieczny nawet wtedy, gdy p jest niezainicjalizowany albo równy NULL.
Niezainicjalizowana pamięć
malloc nie czyści tego, co ci daje. Bajty zawierają to, co było tam wcześniej.
Gdy chcesz dostać blok już wyzerowany, calloc robi to w jednym kroku; zobacz artykuł o calloc i realloc.
free: oddawanie pamięci
free(p) zwraca blok do alokatora. Trzy zasady:
- Przekaż dokładnie ten wskaźnik, który zwrócił
malloc, a nie przesunięty.free(p + 1)to niezdefiniowane zachowanie. - Zwalniaj każdy blok dokładnie raz. Dwa razy to double free, które psuje wewnętrzną ewidencję alokatora.
- Po zwolnieniu wskaźnik jest nieaktualny. Użycie go to use-after-free.
int *p = malloc(sizeof *p);
free(p);
*p = 5; /* use-after-free: niezdefiniowane zachowanie */
free(p); /* double free: niezdefiniowane zachowanie */
Żadna z tych linii nie musi od razu spowodować awarii i właśnie dlatego są niebezpieczne: program może działać przez kilka minut, a potem wywrócić się w zupełnie niezwiązanym miejscu. Tania obrona to wyczyszczenie wskaźnika:
free(p);
p = NULL;
free(NULL) jest jawnie zdefiniowane jako nierobiące nic, więc drugie free(p) po czymś takim jest nieszkodliwe, a *p staje się natychmiastową awarią w miejscu prawdziwego błędu zamiast cichego uszkodzenia danych.
Tablica dynamiczna od początku do końca
Oto kształt prawdziwej alokacji: wczytaj liczbę w czasie działania, ustal na jej podstawie rozmiar bloku, użyj go, zwolnij.
Rzutowanie liczby elementów na size_t ma znaczenie, gdy rozmiary robią się duże: n * sizeof *values ze zwykłym int jako n może się przepełnić, zanim mnożenie w ogóle dotrze do malloc, dając blok znacznie mniejszy niż zamierzony. Mnożenie w size_t tego unika.
Alokacja wewnątrz funkcji
Reguła czasu życia sterty sprawia, że to jest dozwolone, a zwracanie lokalnej tablicy nie:
len + 1 zostawia miejsce na terminator zerowy: to ta sama reguła rozmiaru co wszędzie w stringach. Zwróć też uwagę na komentarz nad funkcją: gdy funkcja zwraca zaalokowaną pamięć, to, kto ją zwalnia, jest częścią jej kontraktu. Zapisanie tego to nie biurokracja; to jedyne, co chroni przed wyciekiem.
Struktury na stercie
Ten sam idiom działa dla każdego typu:
Zauważ, że druga gałąź obsługi błędu zwalnia p przed wyjściem. Gdy funkcja trzyma więcej niż jedną alokację, każda ścieżka błędu musi zwolnić te, które już zajęła: to najczęstsze źródło wycieków opisanych w artykule o wyciekach pamięci.
Lista kontrolna
- Zawsze sprawdzaj wynik
mallocwzględemNULLprzed pierwszym użyciem. - Wyliczaj rozmiar jako
n * sizeof *p, mnożąc wsize_t. - W C nie rzutuj zwracanej wartości.
- Pamięć z
mallocjest niezainicjalizowana; zapisz, zanim odczytasz. - Zwalniaj dokładnie raz, oryginalnym wskaźnikiem, a potem ustaw go na
NULL. - Każda ścieżka błędu po alokacji musi zwolnić to, co już trzyma.
Najczęściej zadawane pytania
Co robi malloc w C?
malloc(n) prosi o n bajtów pamięci ze sterty i zwraca wskaźnik na początek tego bloku albo NULL, jeśli żądania nie da się spełnić. Blok żyje, dopóki nie przekażesz tego samego wskaźnika do free: w przeciwieństwie do zmiennej lokalnej nie jest zwalniany, gdy funkcja zwraca wartość.
Jak używać malloc i free w C?
Zaalokuj przez int *p = malloc(n * sizeof *p);, sprawdź if (p == NULL), zanim go dotkniesz, używaj go jak tablicy, a po zakończeniu wywołaj free(p); dokładnie raz. Ustawienie potem p = NULL zamienia późniejsze przypadkowe użycie w czytelną awarię zamiast cichego uszkodzenia danych.
Dlaczego malloc(n * sizeof *p) jest lepsze niż malloc(n * sizeof(int))?
Bo sizeof *p automatycznie podąża za typem wskaźnika. Jeśli p stanie się później long * albo struct Point *, rozmiar alokacji sam się zaktualizuje; wpisanie sizeof(int) zostawia rozmiar, który teraz jest błędny, a kompilator tego nie zgłosi.
Co się stanie, jeśli nie zwolnisz pamięci w C?
Blok pozostaje zaalokowany przez cały czas życia procesu: to wyciek pamięci. Krótkiemu programowi to uchodzi na sucho, bo system operacyjny odzyskuje wszystko przy wyjściu, ale program działający długo traci trochę pamięci w każdym obiegu i w końcu ją wyczerpuje. Zobacz wycieki pamięci.