Durch die spontane Speicherzuweisung wachsen und schrumpfen C-Programme während ihrer Ausführung. Sie kennen die genauen Anforderungen Ihrer Anwendung erst, wenn sie tatsächlich ausgeführt wird. Die Lösung ist der Haufen. Sie beantragen eine Sperrung. Das Betriebssystem reserviert es. Du nutzt es. Dann geben Sie es mit „kostenlos“ zurück. Dieser Zyklus ermöglicht es anderen Programmen, denselben RAM wiederzuverwenden.
Betrachten Sie dieses einfache Beispiel. Es ist die grundlegendste Interaktion mit dem Heap, die Sie bekommen können.
„c
int main() {
int p;
p = (int )malloc(sizeof(int));
if (p == 0) {
printf(“FEHLER: Nicht genügend Speicher\n”);
Rückgabe 1;
}
p = 5;
printf(“%d\n”, p);
frei(p);
0 zurückgeben;
}
„
Wie Malloc tatsächlich funktioniert
Die Funktion „malloc“ ist keine Zauberei. Es ist ein Mechaniker, der das Inventar überprüft. Wenn Sie es aufrufen, passieren drei Dinge nacheinander.
- Es prüft die aktuelle Kapazität des Heaps. Es prüft den von Ihnen übergebenen Parameter – in diesem Fall „sizeof(int)“, der 4 Bytes beträgt. Es wird gefragt, ob genügend zusammenhängender Platz vorhanden ist. Wenn der Heap fragmentiert oder voll ist, schlägt er fehl.
- Wenn Platz vorhanden ist, wird dieser Block reserviert. Das System markiert es als „Ihres“. Kein anderer „malloc“-Aufruf kann diesen Block berühren. Es verhindert versehentliche Kollisionen zwischen Zuordnungen.
- Es gibt die Adresse dieses Blocks zurück. Die Daten selbst werden Ihnen nicht angezeigt. Es gibt Ihnen eine Zeigervariable. Diese Variable enthält den Standort. Sie haben jetzt ein Handle für eine bestimmte Stelle im RAM.
In unserem Beispiel erhält „p“ diese Adresse. Die Zeigervariable ist lediglich ein Container für einen Speicherort. Der tatsächliche ganzzahlige Wert befindet sich an dieser Stelle.
Warum Sie nach NULL suchen müssen
Sie sehen die Prüfung „if (p == 0)“ unmittelbar nach der Zuweisung. Einige Anfänger überspringen dies. Es ist ein fataler Fehler.
Überprüfen Sie immer den Zeiger nach jedem Aufruf von malloc, um sicherzustellen, dass der Zeiger gültig ist.
Die Heap-Größe schwankt ständig. Es hängt davon ab, welche anderen Prozesse ausgeführt werden. Es hängt davon ab, wie viel Speicher sie belegt haben. Es gibt keine Garantie dafür, dass ein Anruf erfolgreich ist. Wenn „malloc“ fehlschlägt, wird Null zurückgegeben. Null ist „NULL“. Wenn Sie versuchen, einen Nullzeiger zu dereferenzieren, stürzt Ihr Programm ab. Hart.
Sie könnten „if (p == NULL)“ oder „if (!p)“ schreiben. Sie meinen dasselbe. Aber Sie müssen es überprüfen. Jedes Mal.
Was passiert, wenn Sie vergessen, Speicher freizugeben?
Wenn Ihr Programm endet und Sie nicht „free“ aufgerufen haben, bereinigt das Betriebssystem. Es gibt den ausführbaren Speicherplatz frei. Der Stapel wird geleert. Es gewinnt das globale Gedächtnis zurück. Außerdem werden alle ausstehenden Heap-Zuweisungen recycelt.
Ist es also in Ordnung, am Ende eines Programms Speicherverluste zu verursachen? Technisch gesehen ja. Das Betriebssystem übernimmt die Bereinigung. Es entstehen keine langfristigen Folgen für die Maschine.
Aber es ist eine schlechte Form. Und was noch wichtiger ist: Speicherlecks während der Ausführung sind tödlich. Wenn Sie weiterhin zuweisen, ohne es freizugeben, verhungern Sie Ihren eigenen Prozess. Der Haufen füllt sich. Neue Zuweisungen scheitern. Das Programm stirbt durch Ersticken, nicht durch einen Absturz.
Zeiger im Vergleich zu den Werten, auf die sie verweisen
Hinweise können verwirrend sein. Sie unterscheiden sich von den Werten, auf die sie sich beziehen. Hier sehen Sie, wie sich das in der Praxis auswirkt.
Erstes Szenario. Sie weisen einen Block zu. Sie richten zwei Variablen darauf.
„c
void main() {
int p, q;
p = (int )malloc(sizeof(int));
q = p;
p = 10;
printf(“%d\n”, q);
q = 20;
printf(“%d\n”, *q);
}
„
Ausgabe:
10
20
Warum? „p“ und „q“ sind unterschiedliche Zeiger. Aber „q = p“ lässt sie auf den gleichen Speicherblock verweisen. Wenn Sie den Wert durch „p“ ändern, wird er für „q“ geändert. Wenn Sie es durch „q“ ändern, wird es durch „p“ ersetzt. Es handelt sich um zwei Griffe an derselben Tür.
Zweites Szenario. Sie weisen zwei separate Blöcke zu.
„c
void main() {
int p, q;
p = (int )malloc(sizeof(int));
q = (int )malloc(sizeof(int));
p = 10;
q = 20;
p = q;
printf(“%d\n”, *p);
}
„
Ausgabe:
20
Hier zeigen „p“ und „q“ auf unterschiedliche Orte. „q“ enthält 20. „ p = q“ kopiert den Wert* 20 an die Stelle, auf die „p“ zeigt. Der Zeiger „p“ selbst bewegt sich nicht. Der Wert an der Adresse von „p“ ändert sich.
Die Unterscheidung ist wichtig. Man ändert die in der Variablen gespeicherte Adresse. Der andere ändert die Daten an dieser Adresse. Eine Verwechslung der beiden führt zu logischen Fehlern, die schwieriger zu debuggen sind als Syntaxfehler.


Hinweise, Erinnerung und der freie Fall
Die Ausgabe ist 20. Zeile 6 sagt alles.
Man könnte meinen, „p = q“ und „p = q“ bewirken dasselbe. Das tun sie nicht. Man verschiebt Daten. Die anderen verschieben Adressen. Mit dem Compiler können Sie „p = q“ zuweisen, da beide dereferenzierten Zeiger Ganzzahlen sind. Es ist ein direkter Werteaustausch. Aber p = q? Das ist anders. Es zeigt „p“ auf genau denselben Speicherblock wie „q“. Die Adresse wird verschoben. Die Daten bleiben erhalten.
Wenn die Typen nicht übereinstimmen, blockiert der Compiler dies. Ein Zeiger auf eine Ganzzahl kann nicht auf eine Zeichenfolge zeigen. Die Typen müssen übereinstimmen.
Vier Möglichkeiten zur Initialisierung
Eine neue Zeigervariable ist eine Belastung, bis sie initialisiert wird.
int *p; erstellt einen Zeiger, der auf keinen bestimmten Punkt zeigt. Es deutet auf Müll hin. Es ist ein Fehler, es zu dereferenzieren. Sie benötigen einen bekannten Standort. Es gibt vier Möglichkeiten dorthin zu gelangen.
- malloc : Dadurch wird ein Block auf dem Heap zugewiesen. „p“ enthält jetzt eine gültige Adresse. Der Zeiger wird initialisiert, weil er auf einen bestimmten, reservierten Speicherabschnitt zeigt.
- Zuweisung :
p = q;wennqgültig ist, wirdpgültig. Es erbt die Adresse von „q“. Wenn „q“ Müll ist, erhält „p“ auch Müll. - Explizite Adresse : Sie können „p“ direkt auf eine bekannte Variable verweisen. „p = &i;“ zeigt „p“ auf die Ganzzahl „i“. Einfach. Direkt.
- Null/NULL : „p = 0;“ oder „p = NULL;“. Dadurch wird die Adresse auf Null gesetzt. Es deutet nicht auf einen Block hin. Es zeigt auf den Nullort.
Warum Null verwenden? Es ist eine Flagge.
Sie können „if (p == 0)“ überprüfen, um zu sehen, ob der Zeiger gültig ist. Das System erkennt dies. Wenn Sie versuchen, einen Nullzeiger zu dereferenzieren, stürzt das Programm ab. Hart.
p = 0; *p = 5;
Die zweite Zeile schlägt fehl. „p“ zeigt auf nichts. Man kann nicht an nichts schreiben. Dieses Verhalten ist später für verknüpfte Listen von entscheidender Bedeutung. Es ist ein Sicherheitsnetz.
Den Haufen befreien
Es reicht nicht aus, Speicher zuzuweisen. Du musst es zurückgeben.
„malloc“ schnappt sich Platz. „free“ gibt es zurück.
„free(p)“ macht zwei Dinge. Zunächst wird die Reservierung des Blocks aufgehoben. Der Heap holt sich diese Erinnerung zurück. Es kann wiederverwendet werden. Zweitens bleibt „p“ nicht initialisiert. Es wird nicht auf magische Weise sicher. Sie müssen „p“ neu initialisieren, bevor Sie es erneut verwenden.
Der Block ist aus Ihrem Bereich verschwunden. Der Zeiger baumelt.
Hier ist das Standardmuster für die Verarbeitung einer einzelnen Ganzzahl auf dem Heap:
„c
int main() {
int p;
p = (int )malloc(sizeof(int));
p = 10;
printf(“%d\n”, p);
frei(p);
0 zurückgeben;
}
„
Dies ist größtenteils eine Demo. Aber es zeigt den Lebenszyklus.
„sizeof(int)“ gibt die Größe in Bytes zurück. Auf den meisten Maschinen sind das 4. Sie könnten „malloc(4)“ fest codieren. Nicht. „sizeof“ macht Code portierbar. Es liest sich besser. Es passt sich an, wenn sich die Architektur ändert.
„malloc“ gibt einen generischen Zeiger zurück. Compiler warnen oft, wenn Sie es nicht umwandeln. „(int *)“ konvertiert diesen generischen Blob in einen Zeiger auf eine Ganzzahl. Es entspricht „p“.
„free(p)“ übergibt den Block zurück an den Heap.
Strukturen und Operatorpriorität
Strukturen verhalten sich genauso. Sie weisen der gesamten Struktur einen Block zu, nicht nur den Feldern.
„c
struct rec {
int i;
float f;
char c;
};
int main() {
struct rec p;
p = (struct rec )malloc(sizeof(struct rec));
(p).i = 10;
( p).f = 3,14;
(p).c = ‘a’;
printf(“%d %f %c\n”, ( p).i, (p).f, ( p).c);
frei(p);
0 zurückgeben;
}
„
Schauen Sie sich „(*p).i = 10;“ an.
Warum die Klammern?
Weil „.“ eine höhere Priorität hat als „*“.
Ohne Klammern bedeutet „p.i“ „ (p.i)“. Es versucht, auf „i“ auf dem Zeiger „p“ zuzugreifen und diesen dann zu dereferenzieren. Das ist nicht das, was Sie wollen. Sie möchten zuerst „p“ dereferenzieren und dann auf „i“ zugreifen.
Die Vorrangregeln sind streng. „*“ bindet in diesem Zusammenhang enger als „.“ Klammern erzwingen, dass die Dereferenzierung vor dem Memberzugriff erfolgt.
Die meisten Menschen hassen es, wiederholt „(*p).i“ einzugeben. Es ist ausführlich.
C hat eine Abkürzung.
p->i
Es ist genau das Gleiche wie „(*p).i“. Aber es ist schneller zu tippen. Sie werden „->“ überall im C-Code sehen. Dies ist die Standardmethode für den Zugriff auf Strukturmitglieder über einen Zeiger.
Der Heap ist eine endliche Ressource. Sorgfältig verteilen. Konsequent die Zuordnung aufheben. Oder sehen Sie zu, wie Ihre Erinnerungen verschwinden.


























