FAQ:Speicherbelegung (dynamischer Speicher)

[ FAQ in de.comp.lang.c ]


Diese FAQ bezieht sich in ihrer Gänze auf den inzwischen nicht mehr aktuellen ISO-C Standard 9899:1990, vielfach auch als C90 bezeichnet. Der seit Dezember 1999 existierende neue ISO 9899:1999 Standard (oder auch C99) wird nicht berücksichtigt.


[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


Frage 3.1: Warum funktioniert dieser Schnippsel nicht?

	char *ergebnis;
	printf("Gib etwas ein:\n");
	gets(ergebnis);
	printf("Du hast \"%s\" eingegeben.\n", ergebnis);

Antwort: Die Zeiger-Variable "ergebnis" soll beim Aufruf von gets() auf einen Speicherbereich zeigen, in den gets() die eingelesenen Daten schreiben kann. Beim Aufruf von gets() zeigt "ergebnis" aber nicht auf einen gültigen Speicherbereich. (Da lokale Variablen nicht initialisiert werden und normalerweise zufällige Werte enthalten, ist es noch nicht einmal sicher, dass "ergebnis" ein NULL-Zeiger ist. Siehe hierzu 17.1)

Das obige Programm läßt sich leicht korrigieren, indem man ein lokales Feld statt eines Zeigers benutzt und es dem Compiler überläßt, sich um die Speicherbelegung zu kümmern. Etwa:

	#include <stdio.h>
	#include <string.h>

	char ergebnis[100], *p;
	printf("Gib etwas ein:\n");
	fgets(ergebnis, sizeof(ergebnis), stdin);
	if((p = strchr(ergebnis, '\n')) != NULL)
		*p = '\0';
	printf("Du hast \"%s\" eingegeben.\n", ergebnis);

In diesem Beispiel wurde außerdem fgets() statt gets() benutzt (siehe hierzu 11.6). Dadurch wird es möglich, die Größe des Feldes anzugeben, damit das Ende des Feldes nicht überschrieben wird, wenn der Benutzer eine Zeile eingibt, die zu lang ist. (Unglücklicherweise - für dieses Beispiel - löscht fgets() im Gegensatz zu gets() das abschließende '\n' nicht.) Eine weitere Möglichkeit, das Programm zu korrigieren, besteht darin malloc() zu verwenden, um den Puffer für die Eingabe zu reservieren.


Frage 3.2: strcat funktioniert einfach nicht. Ich benutze

	char *s1 = "Hallo, ";
	char *s2 = "Welt!";
	char *s3 = strcat(s1, s2);

und bekomme sehr seltsame Ergebnisse.

Antwort: Das Problem besteht wieder darin, dass der Speicher für das verkettete Ergebnis nicht belegt wurde. C kennt keinen automatisch verwalteten String-Typ. C Compiler belegen nur dann Speicher für Objekte, wenn diese ausdrücklich im Quelltext enthalten sind (im Falle von Strings trifft dies auf String-Literale und character-Felder zu). Der Programmierer muß (ausdrücklich) dafür sorgen, dass zur Laufzeit für die Ergebnisse von Operationen wie String-Verkettungen Speicher bereitgestellt wird. Hierzu benutzt er normalerweise Felder oder malloc(). (Siehe hierzu auch 17.20)

strcat() übernimmt keine Speicherbelegung; der zweite String wird an den ersten angehängt (der erste String bleibt dabei in dem Speicherbereich, in dem er vorher war). Eine einfache Lösung des Problems besteht somit darin, den erste String als Feld zu definieren, das groß genug ist, um auch den verketteten String zu enthalten:

	char s1[20] = "Hallo, ";

Da strcat() den Wert seines ersten Argumentes zurückgibt (in diesem Fall s1), ist s3 überflüssig.

Siehe hierzu: CT&P Abschn. 3.2 Seite 32.


Frage 3.3: Aber "man strcat" sagt, strcat nimmt zwei Argumente vom Typ char *. Wie soll ich wissen, dass ich mich um die Speicherbelegung kümmern muß?

Antwort: Hierzu gilt die Daumenregel: Wenn Zeiger benutzt werden, muß man sich immer Gedanken über die Speicherbelegung machen. Wenn der Compiler den Speicher nicht automatisch reserviert, mußt malloc() benutzt werden. Wenn die Dokumentation einer Bibliotheksfunktion nicht ausdrücklich von Speicherbelegung spricht, bleibt diese Aufgabe normalerweise dem Programmierer überlassen.

Der "Synopsis" Abschnitt am Anfang einer (UNIX-artigen) Manual-Seite kann manchmal irreführend sein. Die Beispiele zeigen eher, wie die Funktion definiert wurde, nicht, wie sie aufgerufen wird. Insbesondere erwarten viele Funktionen, die Zeiger-Argumente benutzen, dass der Zeiger auf ein bereits existierendes Objekt (struct oder ein Feld) zeigt (siehe hierzu 2.3 und 2.4); typische Beispiele hierfür sind time() und stat().


Frage 3.4: Meine Funktion soll einen String zurückgeben, aber ihr Rückgabewert enthält nur Datenmüll.

Antwort: Die Funktion gibt einen Zeiger auf einen Speicherbereich zurück; es muß sichergestellt sein, dass dieser Speicher auch korrekt reserviert wurde. Der zurückgegebene Zeiger sollte auf einen statischen Puffer, einen beim Aufruf der Funktion als Argument übergebenen Puffer oder mittels malloc() reservierten Speicher zeigen. Er darf nie auf ein lokales (auto) Feld zeigen, da dieses nicht mehr existiert, wenn die Funktion verlassen wird. In anderen Worten, Deine Funktion sollte niemals so aussehen:

	char *f()
	{
		char buf[10];
		/* ... */
		return buf;
	}

Eine Möglichkeit (die nicht perfekt ist, sie funktioniert beispielsweise nicht, wenn f() rekursiv aufgerufen wird) besteht darin, den Puffer als statisches (static) Feld zu deklarieren:

		static char buf[10];

Siehe auch 17.5.


Frage 3.5: Warum benutzen manche Programme ausdrückliche Typkonversionen, um den Rückgabewert von malloc() in eine Zeiger des Typs zu verwandeln, für den Speicher alloziert wurde?

Antwort: Bevor der ANSI/ISO Standard für C den Zeiger-Typ void * einführte, brauchte man solche Konversionen, damit der Compiler keine Warnungen über inkompatible Zeiger-Typen produzierte. (Mit ANSI/ISO C sind die Konversionen nicht mehr nötig.)

Hinweis von Uz:
Eine explizite Konversion kann auch dann sinnvoll sein, wenn das Modul mit moeglichst wenig Änderungen in einem C++ Projekt benutzt oder später als C++ Code übersetzt werden soll. C++ konvertiert void* Zeiger nicht automatisch in typisierte Zeiger, d.h. ein Aufruf von malloc() ohne Cast des Rückgabewertes in einen typisierten Zeiger erzeugt bei der Übersetzung mit einem C++ Compiler einen Fehler. Wie wichtig dieser Grund ist muß im Einzelfall entschieden werden, es gibt auch Argumente, die gegen eine explizite Konvertierung sprechen.


Frage 3.6: Mein Programm stürzt - scheinbar innerhalb eines Aufrufs von malloc() ab, aber ich kann den Fehler nicht finden.

Antwort: Es ist leider zu einfach, die internen Daten, die malloc() verwendet, um den Speicher zu verwalten, zu zerstören. Die Ursache dafür läßt sich oft nur durch langwieriges und mühsames Suchen finden. Ein besonders häufig auftretender Fehler besteht darin, dass man in einen durch malloc() gewonnenen Bereich mehr Daten schreiben will, als dieser fassen kann (beispielsweise wenn der Platz für einen String mittels malloc(strlen(s)) statt malloc(strlen(s)+1) alloziert wurde). Andere übliche Fehler sind, einen Zeiger auf bereits freigegebenen Speicher zu benutzen, einen Speicherbereich zweimal freizugeben, einen Zeiger auf einen Bereich, der nicht mit malloc() belegt wurde, als Argument an free() zu geben oder ein realloc für einen NULL-Zeiger zu verwenden (siehe 3.13).

Es existiert eine Reihe von Programmpaketen, die helfen, derartige Probleme mit malloc zu entdecken; populär ist "dbmalloc" von Conor P. Cahill oder "leak", das in volume 27 des comp.sources.unix archives; JMalloc.c und JMalloc.h in Fidonet C_ECHO Snippets (oder archie fragen; siehe frage 17.12); darüberhinaus MEMDEBUG von ftp.crpht.lu in pub/sources/memdebug. Siehe auch 17.12.


Frage 3.7: Wenn dynamisch allozierter Speicher freigegeben wurde, kann man ihn nicht mehr benutzen. Oder doch?

Antwort: Nein. Einige frühe Dokumentationen für malloc() behaupten zwar, dass freigegebene Speicherbereiche ihren Inhalt nicht ändern, aber dieses Verhalten war nie portabel und wird vom ANSI Standard nicht gefordert.

Nur wenige Programmierer werden freigegebenen Speicher absichtlich benutzen, aber es ist sehr einfach, dies unbeabsichtigt zu tun. Das folgende Beispiel zeigt, wie man eine verkettete Liste (richtig) freigibt:

	struct liste *listp, *nextp;
	for(listp = anfang; listp != NULL; listp = nextp) {
		nextp = listp->next;
		free((char *)listp);
	}

Auf den ersten Blick scheint es natürlicher, die Iterationsvorschrift in der for-Schleife in der Form listp = listp->next zu schreiben. In diesem Fall würde aber auf listp zugegriffen, obwohl der zugehörige Speicher bereits freigegeben wurde.

Siehe hierzu: ANSI Rationale Abschnitt 4.10.3.2 Seite 102; CT&P Abschnitt 7.10 Seite 95.


Frage 3.8: Ich reserviere Speicher für structs, die Zeiger auf weitere dynamisch belegte Objekte enthalten. Müssen die anderen Objekte, auf die Zeiger meiner Struktur zeigen, freigegeben werden, bevor ich die Struktur freigeben kann?

Antwort: Ja. Im allgemeinen muß man immer dafür sorgen, dass jeder Zeiger, der von malloc() zurückgegeben wurde, genau einmal (wenn der Speicher überhaupt freigegeben wird) an free() als Argument übergeben wird.


Frage 3.9: Muß ich allen dynamisch belegten Speicher am Ende des Programms wieder freigeben?

Antwort: Ein richtiges Betriebssystem organisiert den Speicher nach dem Ende eines Programmes neu, dann muß der Speicher nicht freigegeben werden. Von einigen Personal-Computern ist allerdings bekannt, dass es ihnen nicht immer gelingt, den kompletten Speicher wieder verfügbar zu machen, in diesem Fall sollte sich der Programmierer darum kümmern. Der ANSI/ISO Standard erklärt hierzu, es sei eine Frage der Implementationsqualität.

Siehe hierzu: ANSI Abschnitt 4.10.3.2


Frage 3.10: Ich habe ein Programm geschrieben, das große Speicherbereiche dynamisch belegt und dann wieder freigibt. Wenn ich mir (mit ps) anschaue, wieviel Speicher mein Programm benötigt, so ändert der sich allerdings nicht.

Antwort: Die meisten Implementationen von malloc/free geben freigegebenen Speicher nicht an das Betriebssystem (sofern es eines gibt) zurück, sondern benutzen diesen Speicher für spätere Aufrufe von malloc() im selben Prozeß.


Frage 3.11: Woher weiß free(), wie viele Bytes es freigeben soll?

Antwort: Das malloc/free Paket merkt sich die Größe jedes Speicherblocks, der belegt wurde. Es ist also nicht nötig, die Größe des Blocks an free() weiterzugeben.


Frage 3.12: Gibt es dann einen Weg, vom malloc-Paket zu erfahren, wie groß ein belegter Block ist?

Antwort: Keinen portablen.


Frage 3.13: Darf ich einen NULL-Zeiger als erstes Argument an realloc() übergeben? Wozu soll das gut sein?

Antwort: ANSI C erlaubt dies (und das dazu verwandte realloc(..., 0), das den Speicher freigibt), aber einige frühe Implementationen unterstützen es nicht, es ist also nicht völlig portabel. Einen NULL-Zeiger beim ersten Aufruf von realloc() zu verwenden, kann einen Algorithmus, der ohne Initialisierung fortschreitend Speicher belegt, vereinfachen.

Siehe hierzu: ANSI Abschnitt 4.10.3.4


Frage 3.14: Worin besteht der Unterschied zwischen calloc() und malloc()? Füllt calloc() auch Zeiger- und Gleitkomma-Felder mit Null-Werten? Brauche ich ein cfree() oder kann free() auch mit calloc() reservierte Speicherbereiche freigeben?

Antwort: calloc(m, n) ist eigentlich äquivalent zu

	p = malloc(m * n);
	memset(p, 0, m * n);

calloc() füllt den Speicher mit binären Nullen (alle Bits sind Null). Gleitkomma- und Zeiger-Nullen (siehe Abschnitt 1 dieser FAQ) können aber ganz anders repräsentiert werden (und werden es oft auch), weshalb man sich für solche Felder nicht auf die Nullen verlassen darf. free() kann (und sollte) benutzt werden, um den mit calloc() allozierten Speicher wieder freizugeben.

Siehe hierzu: ANSI Abschnitte 4.10.3 bis 4.10.3.2


Frage 3.14: Was ist alloca() und warum soll man es nicht benutzen?

Antwort: alloca() belegt Speicher, der automatisch freigegeben wird, wenn die Funktion, in der alloca() benutzt wurde, beendet wird. Speicher, der mit alloca() alloziert wurde, ist also nur im Kontext dieser Funktion korrekt belegt.

alloca() läßt sich nicht portabel implementieren, auf Maschinen ohne Stack ist dies recht schwierig. Sehr problematisch wird es, wenn man einen Zeiger auf mittels alloca() belegten Speicher an eine Funktion direkt weitergibt, etwa fgets(alloca(100), 100, stdin) (die natürlich erscheinende Implementation auf einer Maschine mit Stack funktioniert hier nicht).

Aus diesen Gründen kann alloca() nicht in Programmen verwendet werden, die auf viele Plattformen portiert werden sollen, unabhängig davon, wie nützlich die Funktion sein mag.

Siehe hierzu: ANSI Rationale Abschnitt 4.10.3 Seite 102.

[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


[ FAQ Logo ]   © 1997-2004 Jochen Schoof (joscho@bigfoot.de)
Diese Version wurde am 14. März 2004 erzeugt. Sie wird zukünftig nicht weiter gepflegt.