Kiedy powinienem używać malloc w C, a kiedy nie?


94

Rozumiem, jak działa malloc (). Moje pytanie brzmi: zobaczę takie rzeczy:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

Ze względu na zwięzłość pominąłem sprawdzanie błędów. Moje pytanie brzmi: czy nie możesz po prostu zrobić powyższego, inicjując wskaźnik do jakiejś statycznej pamięci w pamięci? być może:

char *some_memory = "Hello World";

W którym momencie faktycznie musisz samodzielnie przydzielić pamięć zamiast deklarować / inicjować wartości, które chcesz zachować?


5
Re: Ze względu na zwięzłość pominąłem sprawdzanie błędów - niestety zbyt wielu programistów pomija sprawdzanie błędów, ponieważ nie zdają sobie sprawy, że malloc()może zawieść!
Andrew,

Odpowiedzi:


133
char *some_memory = "Hello World";

tworzy wskaźnik do stałej łańcuchowej. Oznacza to, że ciąg „Hello World” będzie znajdował się gdzieś w części pamięci przeznaczonej tylko do odczytu, a Ty masz do niego po prostu wskaźnik. Możesz użyć ciągu jako tylko do odczytu. Nie możesz wprowadzać w nim zmian. Przykład:

some_memory[0] = 'h';

Prosi o kłopoty.

Z drugiej strony

some_memory = (char *)malloc(size_to_allocate);

alokuje tablicę char (zmienną) i some_memory punktów do tej przydzielonej pamięci. Teraz ta tablica jest zarówno do odczytu, jak i do zapisu. Możesz teraz:

some_memory[0] = 'h';

a zawartość tablicy zmieni się na „hello World”


19
Dla wyjaśnienia, chociaż podoba mi się ta odpowiedź (dałem ci +1), możesz zrobić to samo bez malloc (), używając po prostu tablicy znaków. Coś w rodzaju: char some_memory [] = "Hello"; some_memory [0] = 'W'; również zadziała.
randombits

19
Masz rację. Możesz to zrobić. Kiedy używasz malloc (), pamięć jest alokowana dynamicznie w czasie wykonywania, więc nie musisz naprawiać rozmiaru tablicy w czasie kompilacji, możesz również zwiększyć lub zmniejszyć ją za pomocą realloc () Żadna z tych rzeczy nie może być zrobiona, gdy to zrobisz: char some_memory [] = "Cześć"; Tutaj, mimo że możesz zmienić zawartość tablicy, jej rozmiar jest stały. W zależności od potrzeb możesz użyć jednej z trzech opcji: 1) wskaźnik do stałej typu char 2) tablica alokowana dynamicznie 3) tablica o stałym rozmiarze i czasie kompilacji.
codaddict

Aby podkreślić, że jest to tylko do odczytu, powinieneś napisać const char *s = "hi";Czy nie jest to faktycznie wymagane przez standard?
Do Theis

1
@Till, nie, ponieważ zadeklarowałeś wskaźnik zainicjalizowany na adres bazowy ciągu literału „hi”. s można ponownie przypisać idealnie legalnie, aby wskazywały na znak inny niż stały. Jeśli chcesz mieć stały wskaźnik do ciągu tylko do odczytu, potrzebujeszconst char const* s;
Rob11311

38

W tym dokładnym przykładzie malloc jest mało przydatny.

Głównym powodem, dla którego Malloc jest potrzebny, jest to, że masz dane, które muszą mieć okres istnienia inny niż zakres kodu. Twój kod wywołuje malloc w jednej procedurze, przechowuje gdzieś wskaźnik i ostatecznie wywołuje free w innej procedurze.

Drugim powodem jest to, że C nie ma możliwości sprawdzenia, czy na stosie zostało wystarczająco dużo miejsca na alokację. Jeśli twój kod musi być w 100% niezawodny, bezpieczniej jest użyć malloc, ponieważ wtedy twój kod może rozpoznać błąd alokacji i obsłużyć go.


4
Cykle życia pamięci i związane z nimi pytanie, kiedy i jak ją zwolnić, są ważnym problemem w przypadku wielu popularnych bibliotek i składników oprogramowania. Zwykle mają dobrze udokumentowaną regułę: „Jeśli przekażesz wskaźnik do tej jednej z moich procedur, musisz ją mieć malloc'd. Śledzę to i zwolnię, gdy skończę. " Częstym źródłem paskudnych błędów jest przekazanie wskaźnika do statycznie przydzielonej pamięci do takiej biblioteki. Gdy biblioteka próbuje ją zwolnić (), program ulega awarii. Ostatnio spędziłem dużo czasu naprawiając błąd taki jak ten, który napisał ktoś inny.
Bob Murphy

Czy chcesz powiedzieć, że jedyny przypadek, w którym malloc () jest używany w praktyce, ma miejsce wtedy, gdy istnieje segment kodu, który będzie wywoływany wiele razy w trakcie życia programu, który będzie nazywany wiele razy i musi być 'wyczyszczony' () towarzyszy free ()? Na przykład w grze takiej jak koło fortuny, gdzie po odgadnięciu i umieszczeniu danych wejściowych w wyznaczonej tablicy znaków, tablica o rozmiarze malloc () może zostać zwolniona do następnego zgadnięcia?
Smith Will Suffice

Żywotność danych jest rzeczywiście prawdziwym powodem korzystania z malloc. Załóżmy, że abstrakcyjny typ danych jest reprezentowany przez moduł, deklaruje typ listy i procedury dodawania / usuwania elementów z listy. Te wartości pozycji wymagają skopiowania do dynamicznie przydzielonej pamięci.
Rob11311

@Bob: te paskudne błędy, stwórz konwencję, że alokator zwalnia pamięć o wiele lepszą, w końcu możesz ją recyklingować. Załóżmy, że przydzieliłeś pamięć za pomocą calloc, aby poprawić lokalizację odniesień, co ujawnia zepsuty charakter tych bibliotek, ponieważ musisz wywołać wolny tylko raz dla całego bloku. Na szczęście nie musiałem używać bibliotek, które określają pamięć jako 'malloc-ed'. To nie jest tradycja POSIX i najprawdopodobniej zostałby uznany za błąd. Jeśli „wiedzą”, że musisz używać malloc, dlaczego biblioteka nie robi tego za Ciebie?
Rob11311

17

malloc to wspaniałe narzędzie do alokacji, ponownego przydzielania i zwalniania pamięci w czasie wykonywania, w porównaniu do statycznych deklaracji, takich jak przykład Hello world, które są przetwarzane w czasie kompilacji i dlatego nie można ich zmienić.

Malloc jest zatem zawsze przydatny, gdy masz do czynienia z danymi o dowolnej wielkości, takimi jak czytanie zawartości plików lub obsługa gniazd, a nie jesteś świadomy długości danych do przetworzenia.

Oczywiście w trywialnym przykładzie, takim jak ten, który podałeś, malloc nie jest magicznym „odpowiednim narzędziem do właściwego zadania”, ale w bardziej złożonych przypadkach (na przykład tworzenie tablicy o dowolnym rozmiarze w czasie wykonywania) jest to jedyny sposób, aby udać się.


7

Jeśli nie znasz dokładnego rozmiaru pamięci, której potrzebujesz, potrzebujesz alokacji dynamicznej ( malloc). Przykładem może być sytuacja, gdy użytkownik otwiera plik w Twojej aplikacji. Będziesz musiał wczytać zawartość pliku do pamięci, ale oczywiście nie znasz z góry rozmiaru pliku, ponieważ użytkownik wybiera plik na miejscu, w czasie wykonywania. Zasadniczo potrzebujesz tego, mallocgdy nie znasz z góry rozmiaru danych, z którymi pracujesz. Przynajmniej jest to jeden z głównych powodów używania malloc. W twoim przykładzie z prostym ciągiem znaków, którego rozmiar już znasz w czasie kompilacji (a ponadto nie chcesz go modyfikować), nie ma sensu dynamiczne przydzielanie tego.


Trochę nie na temat, ale ... musisz bardzo uważać, aby nie spowodować wycieków pamięci podczas używania malloc. Rozważ ten kod:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

Czy widzisz, co jest nie tak z tym kodem? Istnieje warunkowa instrukcja powrotu między malloca free. Na początku może się to wydawać w porządku, ale pomyśl o tym. Jeśli wystąpi błąd, wrócisz bez zwalniania przydzielonej pamięci. Jest to częste źródło wycieków pamięci.

Oczywiście jest to bardzo prosty przykład i bardzo łatwo jest tu dostrzec błąd, ale wyobraź sobie setki wierszy kodu zaśmieconych wskaźnikami, mallocs, frees i wszelkiego rodzaju obsługą błędów. Rzeczy mogą się bardzo szybko pogmatwać. Jest to jeden z powodów, dla których zdecydowanie wolę nowoczesny C ++ od C w odpowiednich przypadkach, ale to zupełnie inny temat.

Więc kiedy używasz malloc, zawsze upewnij się, że twoja pamięć jest freejak największa.


Doskonały przykład! Tak trzymaj ^ _ ^
Musa Al-hassy

6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

jest niedozwolone, literały łańcuchowe są const.

Spowoduje to przydzielenie 12-bajtowej tablicy znaków na stosie lub globalnie (w zależności od tego, gdzie jest zadeklarowana).

char some_memory[] = "Hello World";

Jeśli chcesz zostawić miejsce na dalsze manipulacje, możesz określić, że rozmiar tablicy powinien być większy. (Proszę jednak nie umieszczać 1 MB na stosie.)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);

5

Jednym z powodów, dla których konieczne jest przydzielenie pamięci, jest to, że chcesz ją zmodyfikować w czasie wykonywania. W takim przypadku można użyć malloc lub bufora na stosie. Prosty przykład przypisania „Hello World” do wskaźnika definiuje pamięć, której „zazwyczaj” nie można modyfikować w czasie wykonywania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.