Wann sollte ich malloc in C verwenden und wann nicht?

93

Ich verstehe, wie malloc () funktioniert. Meine Frage ist, ich werde solche Dinge sehen:

#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);

Der Kürze halber habe ich die Fehlerprüfung weggelassen. Meine Frage ist, können Sie das nicht einfach tun, indem Sie einen Zeiger auf einen statischen Speicher im Speicher initialisieren? vielleicht:

char *some_memory = "Hello World";

Ab wann müssen Sie den Speicher tatsächlich selbst zuweisen, anstatt die Werte zu deklarieren / zu initialisieren, die Sie beibehalten müssen?

Randombits
quelle
5
Betreff: Der Kürze halber habe ich die Fehlerprüfung weggelassen - leider lassen zu viele Programmierer die Fehlerprüfung aus, weil sie nicht wissen , dass sie malloc()fehlschlagen können!
Andrew

Antworten:

131
char *some_memory = "Hello World";

erstellt einen Zeiger auf eine Zeichenfolgenkonstante. Das bedeutet, dass sich die Zeichenfolge "Hello World" irgendwo im schreibgeschützten Teil des Speichers befindet und Sie nur einen Zeiger darauf haben. Sie können die Zeichenfolge als schreibgeschützt verwenden. Sie können keine Änderungen daran vornehmen. Beispiel:

some_memory[0] = 'h';

Bittet um Ärger.

Andererseits

some_memory = (char *)malloc(size_to_allocate);

weist diesem zugewiesenen Speicher ein char-Array (eine Variable) und some_memory-Punkte zu. Jetzt ist dieses Array sowohl Lesen als auch Schreiben. Sie können jetzt tun:

some_memory[0] = 'h';

und der Inhalt des Arrays ändert sich zu "Hallo Welt"

Codaddict
quelle
19
Um zu verdeutlichen, wie sehr mir diese Antwort gefällt (ich habe Ihnen +1 gegeben), können Sie dasselbe ohne malloc () tun, indem Sie einfach ein Zeichenarray verwenden. So etwas wie: char some_memory [] = "Hallo"; some_memory [0] = 'W'; wird auch funktionieren.
Randombits
18
Du hast recht. Du kannst das tun. Wenn Sie malloc () verwenden, wird der Speicher zur Laufzeit dynamisch zugewiesen, sodass Sie die Arraygröße zur Kompilierungszeit nicht festlegen müssen. Außerdem können Sie ihn mit realloc () vergrößern oder verkleinern. Keines dieser Dinge kann ausgeführt werden, wenn Sie Folgendes tun: char some_memory [] = "Hallo"; Obwohl Sie den Inhalt des Arrays ändern können, ist seine Größe hier festgelegt. Abhängig von Ihren Anforderungen verwenden Sie eine der drei folgenden Optionen: 1) Zeiger auf char const 2) dynamisch zugewiesenes Array 3) feste Größe, Kompilierungszeit zugewiesenes Array.
Codaddict
Um zu betonen, dass es schreibgeschützt ist, sollten Sie schreiben. const char *s = "hi";Ist dies nicht tatsächlich vom Standard vorgeschrieben?
Bis Theis
@ Bis, nein, weil Sie einen Zeiger deklariert haben, der auf die Basisadresse des String-Literal "hi" initialisiert wurde. s kann rechtlich vollkommen neu zugewiesen werden, um auf ein nicht konstantes Zeichen zu verweisen. Wenn Sie einen konstanten Zeiger auf eine schreibgeschützte Zeichenfolge möchten, benötigen Sieconst char const* s;
Rob11311
38

Für genau dieses Beispiel ist Malloc von geringem Nutzen.

Der Hauptgrund, warum malloc benötigt wird, ist, wenn Sie Daten haben, deren Lebensdauer sich vom Codebereich unterscheidet. Ihr Code ruft malloc in einer Routine auf, speichert den Zeiger irgendwo und ruft schließlich in einer anderen Routine frei auf.

Ein zweiter Grund ist, dass C nicht wissen kann, ob auf dem Stapel noch genügend Platz für eine Zuordnung vorhanden ist. Wenn Ihr Code 100% robust sein muss, ist es sicherer, malloc zu verwenden, da Ihr Code dann erkennen kann, dass die Zuordnung fehlgeschlagen ist, und damit umgehen kann.

R Samuel Klatchko
quelle
4
Speicherlebenszyklen und die damit verbundene Frage, wann und wie die Zuordnung aufgehoben werden soll, sind bei vielen gängigen Bibliotheken und Softwarekomponenten ein wichtiges Thema. Sie haben normalerweise eine gut dokumentierte Regel: "Wenn Sie einen Zeiger auf diese meiner Routinen übergeben, müssen Sie sie malloc haben. Ich werde sie verfolgen und freigeben, wenn ich damit fertig bin. "" Eine häufige Ursache für böse Fehler besteht darin, einen Zeiger auf statisch zugewiesenen Speicher an eine solche Bibliothek zu übergeben. Wenn die Bibliothek versucht, sie freizugeben (), stürzt das Programm ab. Ich habe vor kurzem viel Zeit damit verbracht, einen Fehler zu beheben, wie ihn jemand anderes geschrieben hat.
Bob Murphy
Wollen Sie damit sagen, dass malloc () praktisch nur dann verwendet wird, wenn es ein Codesegment gibt, das während der Programmlaufzeit mehrmals aufgerufen wird, das mehrmals aufgerufen wird und seit malloc "bereinigt" werden muss? () wird von free () begleitet? Zum Beispiel in einem Spiel wie dem Glücksrad, wo, nachdem Sie geraten und die Eingabe in ein bestimmtes char-Array eingefügt haben, das Array in der Größe malloc () für die nächste Vermutung freigegeben werden kann?
Smith wird
Die Lebensdauer der Daten ist in der Tat der wahre Grund für die Verwendung von malloc. Angenommen, ein abstrakter Datentyp wird durch ein Modul dargestellt, deklariert einen Listentyp und Routinen zum Hinzufügen / Löschen von Elementen zur Liste. Diese Elementwerte müssen in den dynamisch zugewiesenen Speicher kopiert werden.
Rob11311
@ Bob: Diese bösen Fehler machen die Konvention, dass der Allokator Speicher freigibt, weit überlegen, schließlich können Sie ihn recyceln. Angenommen, Sie haben mit calloc Speicher zugewiesen, um die Lokalität von Referenzen zu verbessern, wodurch die fehlerhafte Natur dieser Bibliotheken sichtbar wird, da Sie für den gesamten Block nur einmal kostenlos aufrufen müssen. Glücklicherweise musste ich keine Bibliotheken verwenden, die den Speicher als "malloc-ed" spezifizieren. Dies ist keine POSIX-Tradition und wird höchstwahrscheinlich als Fehler angesehen. Wenn sie "wissen", dass Sie malloc verwenden müssen, warum erledigt die Bibliotheksroutine dies nicht für Sie?
Rob11311
17

malloc ist ein wunderbares Tool zum Zuweisen, Neuzuweisen und Freigeben von Speicher zur Laufzeit im Vergleich zu statischen Deklarationen wie Ihrem Hallo-Welt-Beispiel, die zur Kompilierungszeit verarbeitet werden und daher nicht in der Größe geändert werden können.

Malloc ist daher immer dann nützlich, wenn Sie mit Daten beliebiger Größe wie dem Lesen von Dateiinhalten oder Sockets arbeiten und sich der Länge der zu verarbeitenden Daten nicht bewusst sind.

In einem trivialen Beispiel wie dem, das Sie gegeben haben, ist malloc natürlich nicht das magische "richtige Werkzeug für den richtigen Job", aber für komplexere Fälle (z. B. Erstellen eines Arrays beliebiger Größe zur Laufzeit) ist dies der einzige Weg gehen.

moritz
quelle
7

Wenn Sie die genaue Größe des zu verwendenden Speichers nicht kennen, benötigen Sie eine dynamische Zuordnung ( malloc). Ein Beispiel könnte sein, wenn ein Benutzer eine Datei in Ihrer Anwendung öffnet. Sie müssen den Inhalt der Datei in den Speicher lesen, aber natürlich kennen Sie die Größe der Datei nicht im Voraus, da der Benutzer die Datei zur Laufzeit vor Ort auswählt. Grundsätzlich benötigen mallocSie also, wenn Sie die Größe der Daten, mit denen Sie arbeiten, nicht im Voraus kennen. Zumindest ist dies einer der Hauptgründe für die Verwendung malloc. In Ihrem Beispiel mit einer einfachen Zeichenfolge, deren Größe Sie zum Zeitpunkt der Kompilierung bereits kennen (und die Sie nicht ändern möchten), ist es wenig sinnvoll, diese dynamisch zuzuweisen.


Etwas abseits des Themas, aber ... Sie müssen sehr vorsichtig sein, um bei der Verwendung keine Speicherlecks zu verursachen malloc. Betrachten Sie diesen Code:

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;
}

Sehen Sie, was mit diesem Code nicht stimmt? Es gibt eine bedingte Rückgabeanweisung zwischen mallocund free. Es mag zunächst in Ordnung erscheinen, aber denken Sie darüber nach. Wenn ein Fehler auftritt, kehren Sie zurück, ohne den von Ihnen zugewiesenen Speicher freizugeben. Dies ist eine häufige Ursache für Speicherlecks.

Natürlich ist dies ein sehr einfaches Beispiel, und es ist sehr leicht, den Fehler hier zu erkennen, aber stellen Sie sich Hunderte von Codezeilen vor, die mit Zeigern, mallocs, frees und allen Arten der Fehlerbehandlung übersät sind . Die Dinge können sehr schnell sehr chaotisch werden. Dies ist einer der Gründe, warum ich in anwendbaren Fällen modernes C ++ gegenüber C sehr bevorzuge, aber das ist ein ganz anderes Thema.

Stellen Sie daher bei mallocjeder Verwendung sicher, dass Ihr Speicher so wahrscheinlich freewie möglich ist.

adam10603
quelle
Exzellentes Beispiel!
Weiter
6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

ist illegal, String-Literale sind const.

Dadurch wird ein 12-Byte-Zeichenarray auf dem Stapel oder global zugewiesen (je nachdem, wo es deklariert ist).

char some_memory[] = "Hello World";

Wenn Sie Platz für weitere Manipulationen lassen möchten, können Sie festlegen, dass das Array größer sein soll. (Bitte legen Sie jedoch nicht 1 MB auf den Stapel.)

#define LINE_LEN 80

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

Ein Grund, warum der Speicher zugewiesen werden muss, besteht darin, dass Sie ihn zur Laufzeit ändern möchten. In diesem Fall kann ein Malloc oder ein Puffer auf dem Stapel verwendet werden. Das einfache Beispiel für die Zuweisung von "Hello World" zu einem Zeiger definiert Speicher, der "normalerweise" zur Laufzeit nicht geändert werden kann.

Mark Wilkins
quelle