Etwas Nachhilfe in C Speicherreservierung und free

Ich bin verwöhnt vom Garbage Collector in Java und muß ein paar grundsätzliche Fragen klären, damit ich nicht in Speicherlecks renne. Ein paar Beispiele:

Wenn ich ein Array per malloc reserviere:
int* feld;

feld = malloc(sizeof(int) * 10);

reicht es dann, es über free(feld) freizugeben, oder wird dadurch nur der Zeiger freigegeben, das eigentliche Array ist aber noch vorhanden?

Ähnlich: Wenn ich ein Array global deklariert habe als
int feld[20];
reicht dann ein free(feld) ?

Weitere Fragen kann ich erst stellen, wenn die beantwortet sind … =)

Wenn ich ein Array per malloc reserviere:
int* feld;

feld = malloc(sizeof(int) * 10);

reicht es dann, es über free(feld) freizugeben, oder wird
dadurch nur der Zeiger freigegeben, das eigentliche Array ist
aber noch vorhanden?

Ein einfaches free(feld) reicht.

Ähnlich: Wenn ich ein Array global deklariert habe als
int feld[20];
reicht dann ein free(feld) ?

Das darfst du nicht freigeben. Du darfst nur freigeben, was vorher irgendwo per malloc() reserviert worden ist. Deklarationen wie oben werden auf dem Stack abgelegt und sind ein anderes Paar Schuhe.

Beim zweiten Fall habe ich mich vielleicht nicht so genau ausgedrückt: Es ist eine Variable, die ausserhalb einer Funktion deklariert wurde und somit auf dem Heap landet.

Hallo serethos,

Beim zweiten Fall habe ich mich vielleicht nicht so genau
ausgedrückt: Es ist eine Variable, die ausserhalb einer
Funktion deklariert wurde und somit auf dem Heap landet.

Hier musst Du vielleicht ein konkretes
Beispiel dafür bringen, was Du mit
‚auf dem heap landet‘ meinst.

Ein Stück Programm, das das zeigt?

Generell gilt (wie Nicos schon sagte),
nur was mit malloc/calloc angefordert
wird, gibt man mit free wieder frei.

Grüße

CMБ

Ich bin verwöhnt vom Garbage Collector in Java und muß ein
paar grundsätzliche Fragen klären, damit ich nicht in
Speicherlecks renne. …

Hallo,

weitgehend unabhängig von der konkreten Sprache gilt: Funktionen zur Speicherreservierung sind immer paarweise zu verwenden wie malloc und free, new und delete usw. Die Kunst besteht darin, den richtigen Zeitpunkt für die Freigabe zu bestimmen - dass der Wert nicht mehr gebraucht wird, ist klar, schwieriger ist es, dafür zu sorgen, dass die Freigabe immer und genau 1 mal stattfindet. Das gilt im übrigen auch für viele Windowsfunktionen, die Objekte erzeugen, z.B. CreatePen, Freigabe mit DeleteObject.

Vom System selbst erzeugte Speichereservierungen werden auch vom System (Compiler) wieder freigegeben (Globale Variable z.B. am Programmende), hier DARF sich der Programierer nicht einmischen. Lokale Variable in Unterprogrammen (und die Parameter) werden am Ende des Unterprogramms freigegeben - ein beliebter Fallstrick für Anfänger, denn nach dem Return existiert eine lokale Variable nicht mehr und ein Zugriff erzeugt einen Speicherzugriffsfehler.

Gruss Reinhard

Hallo Fragewurm,

Beim zweiten Fall habe ich mich vielleicht nicht so genau
ausgedrückt: Es ist eine Variable, die ausserhalb einer
Funktion deklariert wurde und somit auf dem Heap landet.

Globale Variablen, sind automatisch Static und landen eigentlich im Data-Segment, wie auch alle lokalen Variablen, welche explizit als Static deklariert werden.
Der Speicherbereich für das Data-Segment wird vom Programm-Loader, also durch das Betriebssystem, angelegt. Du solltest also nicht versuchen da reinzupfuschen …

„Normale“ lokale Variablen werden auf dem Stack angelegt und nach beendigung der entsprechenden Funktion automatisch „vernichtet“.

Mit malloc()/calloc() reservierter Speicher wird auf dem Heap abgelegt und muss von dir, durch free(), wieder freigegeben werden.

Soweit mal zum reinen C.

Unter Windows gibt es noch ein paar weitere Dinge zu beachten:

  • Der Heap (lokaler Heap) welcher mit malloc()/calloc() verwaltet wird, wird nach beenden des Programms eigentlich automatisch wieder freigegeben, allerdings sollte man sich nicht darauf verlassen.

  • Windows kennt aber noch einen globalen Heap. Von diesem wird der Speicher für deinen lokalen Heap bezogen und, je nach Platzbedarf, wird dein lokaler Heap durch weiters allozieren von Speicher aus dem globalen Heap vergrössert.
    Nun gibt es aber noch einen API-Aufruf, welcher wie malloc() funktioniert, aber Speicher vom globalen Heap bezieht. Dieser wird beim Programm-Ende dann aber nicht wieder automatisch freigegeben.

  • Desweiteren gibt es einige API-Aufrufe, welche Speicher vom globalen Heap beziehen, welcher dann aber durch dich wieder freigegeben werden muss, indem du die zugehörigen Beendungsfunktionen aufrufen musst.

Durch Fehler bei den letzten beiden Punkten, entstehen dann die Speicher-Löcher.

MfG Peter(TOO)

Unter Windows gibt es noch ein paar weitere Dinge zu beachten:

  • Der Heap (lokaler Heap) welcher mit malloc()/calloc()
    verwaltet wird, wird nach beenden des Programms eigentlich
    automatisch wieder freigegeben, allerdings sollte man sich
    nicht darauf verlassen.

  • Windows kennt aber noch einen globalen Heap. …

Hallo Peter,

das ist glaube ich veraltet, allerdings habe ich auf die Schnelle keinen direkten Beleg dafür gefunden. Soweit ich mich aber erinnern kann, gibt es unter Win32 keine 2 Heaps mehr, sondern nur noch einen 4GByte grossen Prozess-Adressraum. LocalAlloc und GlobalAlloc existieren zwar noch, machen aber in Bezug auf den Speicher keinen Unterschied mehr (fürs Programm schon: Speicher aus LocalAlloc kann nur mit LocalFree freigegeben werden!). Das hat u.A. zur Folge, dass auch Speicher vom Global Heap am Programmende freigegeben wird.

NT, W2k, XP usw. funktionieren deshalb auch viel zuverlässiger als win16, einer der wenigen Punkte, wo es wirklich substantielle Fortschritte gegeben hat.

SELBSTVERSTÄNDLICH sollte man trotzdem Speicher immer selbst freigeben. Es gibt ja auch endlos laufende Programme.

Gruss Reinhard

Ersteinmal danke für die schnellen und klaren Antworten. Nun habe ich aber noch ein spezielleres Problem. Ich habe einen eigenen StringTokenizer geschrieben (funktioniert ähnlich dem strtok aus string.h). Im Gegensatz zum Original wird aber nicht ein Zeiger auf das Element, sondern ein neuer String mit dem Token rückgeliefert. Das mache ich in etwa so:

char\* nextToken(char\* str, char \*delims, char\* ignores)
{
 char\* buf; // buffer containing the resulting token
 ...
 buf = (char\*)calloc(10, sizeof(char));

 (buf wird mit dem Token befüllt ...)

 // rückgeben des Tokens als eigenständiger String..
 return (char \*)realloc(buf, strlen(buf)+1);
}


    
    
    Auf Seiten des Aufrufers werden diese Tokens gesammelt, verwertet und wieder freigegeben:
    
    
        
        char\* tokens[30];
        while( (actToken = nextToken(string, delims, ignores)) != 0 )
        {
         tokens[i++] = actToken;
        }
        
        ...
        
        for(i=0; i
        
        Mein MSVC meldet mir bei 
         buf = (char\*)calloc(10, sizeof(char));
        ein Speicherleck. Innerhlab der nexToken-Funktion gebe ich ja auch bewußt buf nicht frei, damit ich einen gültigen Zeiger rückliefern kann. 
        Beim Aufrufer wird ja nun über
         free(tokens[i])
        der Speicher nachträglich freigegeben. Warum wird also noch das Leck angezeigt?

Hallo,

das ist doch sicher nur eine Warnung? Ein Compiler kann ja nicht ein Programm in alle Verzweigungen verfolgen. In dem Fall must du eben selbst die Verantwortung übernehmen, alles überprüfen und die Warnung ignorieren, wenn du sicher bist, alles wieder freizugeben. Was ist bei einem Programmfehler/Abbruch zwischen Teil 1 und Teil 2?

Gruss Reinhard

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hallo

Ich habe einen eigenen StringTokenizer geschrieben
(funktioniert ähnlich dem strtok aus string.h).

OK

(buf wird mit dem Token befüllt …)
// rückgeben des Tokens als eigenständiger String…
return (char *)realloc(buf, strlen(buf)+1);

Diese letzte Zeile erscheint mir unverständlich.

while( (actToken = nextToken(string, delims, ignores)) != 0 ) {

Hier rufst Du Deine Funktion immer
mit dem selben String auf, das scheint
etwas anderes zu sein, als es vielleicht
(strtok?) intendiert war.

Mein MSVC meldet mir bei
buf = (char*)calloc(10, sizeof(char));
ein Speicherleck. Innerhlab der nexToken-Funktion gebe ich ja
auch bewußt buf nicht frei, damit ich einen gültigen Zeiger
rückliefern kann.

Den gesamten Algorithmus verstehe ich nicht,
weil wahrscheinlich zu wenige Codezeilen ge-
postet wurden. Das kann man eigentlich kaum
etwas dazu sagen.

Kannst Du eventuell den lauffähigen Quelltext
anhängen, damit man sich ein Bild machen kann,
wie es funktionieren soll?

Grüße

CMБ

Hallo Reinhard,

das ist glaube ich veraltet, allerdings habe ich auf die
Schnelle keinen direkten Beleg dafür gefunden. Soweit ich mich
aber erinnern kann, gibt es unter Win32 keine 2 Heaps mehr,
sondern nur noch einen 4GByte grossen Prozess-Adressraum.
LocalAlloc und GlobalAlloc existieren zwar noch, machen aber
in Bezug auf den Speicher keinen Unterschied mehr (fürs
Programm schon: Speicher aus LocalAlloc kann nur mit LocalFree
freigegeben werden!). Das hat u.A. zur Folge, dass auch
Speicher vom Global Heap am Programmende freigegeben wird.

Bei 32-Bit Programmen, wird das Flat-Model verwendet, allerdings sind diese 4GB, welche das Programm sieht, rein virtuell und physikalisch werden nur diejenigen „Speicher-Kacheln“ belegt, welche das Programm auch benötigt.

Allerdings liegt in diesem Speichermodell, genau das Problem, warum noch weitere Speichersegmente verwaltet werden müssen. Diese 4GB virtueller Speicher stehen eigentlich jedem Programm zur exclusiven verfügung und werden mit der Beendigung des Programms aauch wieder freigegeben.

Was aber nun wenn man Daten zwischen Programmen oder dem Betriebssystem austauschen will ??

Dazu müssen dann Speicherblöcke angelegt werden, welche nicht mehr einem einzigen Task zugeordnet werden können und auch nicht einfach vom Betriebssystem freigegeben werden dürfen, wenn eines der Programme beendet wird.

MfG Peter(TOO)

Hallo Fragewurm,

char* nextToken(char* str, char *delims, char* ignores)
{
char* buf; // buffer containing the resulting token

buf = (char*)calloc(10, sizeof(char));

(buf wird mit dem Token befüllt …)

// rückgeben des Tokens als eigenständiger String…
return (char *)realloc(buf, strlen(buf)+1);

realloc() macht wenig Sinn, wenn die Blockgrössen in dieser Grössenordnung liegen. Um den Verwaltungsaufwand in Grenzen zu halten und den Heap nicht unnütz zu zerstückeln, wird der Speicher immer in Blöcken mit einer festen Grösse, bzw. einem vielfachen davon, angelegt.
Ich weiss jetzt nicht welche Blockgrösse Win, bzw. MSC, verwendet, aber es dürfte im Bereich von 1kB liegen, irgendwo sollte das beschrieben sein.
Wenn also die Blockgrösse bei z.B. 1024 liegt, werden immer vielfache von 1024 Byte angelegt.

Wenn du mit xalloc() einen Block von 1 Byte anlegst, dahinter noch einen Block und dann den ersten wieder freigiebst, kann dieser nur wieder verwendet werden wenn du später noch einen weiteren Block mit 1Byte grösse anlegen willst.

Dies ist der Grund wieso viele Interpreter ab und zu eine Garbagecollection durchführen müssen. In C ist das aber nicht möglich da ja an keiner zentralen Stelle bkannt ist welche Zeiger auf Blöcke wo abgelegt und noch gültig sind.

Warum wird also noch das Leck angezeigt?

Der Compiler kann nur auf Prozedur-Ebene analysieren und ihm fehlt deshalb einfach die Übersicht.

Das kann man auch nicht einfach erweitern, wenn man bedenkt, dass in C auch Zeiger auf Funktionen erlaubt sind und diese Zeiger meist erst zur Laufzeit zugewiesen werden.

In deinem Fall würde ich mit #pragma warning …
die Warnung VOR der Funktion ausschalten und direct NACH der Funktion wieder einschalten, bzw. auf den Default-Wert setzen.

Dadurch kannst du dein Programm ohne Warnungen compilieren, im Sourcecode ist diese Stelle auch gleich entsprechend markiert und an anderen Stellen erhälst du eine Warnung, weil du da einen Fehler gemacht hast.

MfG Peter(TOO)

Jaja, fragen ist gut =)
Ich bin halt Java-verwöhnt und muß meine Denkweise wieder komplett umstellen. Deinen Einwand finde ich sehr interessant (ich dachte, die Speicherreservierung würde viel graziler vorgenommen werden), aber letztenendes entwickle ich für ein Mikrocontrollerboard. Deshalb bin ich auch so überpingelich mit den Speicherlecks, denn ich habe den worst case: extrem wenig Speicher und das Programm muß sehr lange iterativ laufen.

Aber am wichtigsten ist mir gewesen, dass meine code-Zeilen nun anscheinend doch kein Speicherleck erzeugen, der Debugger nur nicht gant hinterherkommt.

Hallo fragewurm,

Ich bin halt Java-verwöhnt und muß meine Denkweise wieder
komplett umstellen. Deinen Einwand finde ich sehr interessant
(ich dachte, die Speicherreservierung würde viel graziler
vorgenommen werden), aber letztenendes entwickle ich für ein
Mikrocontrollerboard.

Das meine Hauptumgebung.

Du hast bis jetzt was von MSC geschrieben, ist das der Compiler fürs Target ??
Meine Angaben bezogen sich auf MSC unter Windows.

Wie das auf deinem Board aussieht weiss ich nicht, aber je nach Umgebung kannst du die Granularität der Speicherverwaltung irgendwo einstellen. Allerdings darfst du nicht vergessen, dass für Verwaltungszwecke jeder Block noch einen Vorspann hat (mindestens ein Zeiger auf den nächsten Block, ein Flag ob der Block belegt ist oder nicht und möglicherweise noch weitere Informationen).

Deshalb bin ich auch so überpingelich
mit den Speicherlecks, denn ich habe den worst case: extrem
wenig Speicher und das Programm muß sehr lange iterativ
laufen.

Ich habe auch Programme welche monatelang ohne Unterbruch laufen.
Bei einem Projekt habe ich zwei Heaps, einer im On-Chip-RAM (512Byte, 16Bit, 2 Zyklen) und einen im externen RAM (128KiByte, 8Bit, 3 Zyklen).

Im externen RAM ist die Blockgrösse ein vielfaches von 128 Byte und der Header hat 6 Bytes.
Im OnChip-RAM sind es 32 und 4 Bytes.

Um mir das Debuggen zu erleichtern, leg ich im Header noch die TaskID des Tasks ab, welcher den Block angefordert hat. Solche Tricks kann man halt machen, wenn man das BS auch gleich selber geschrieben hat
:wink:)
Das ganze werkelt auf dem H8/300H von Renesas (früher Hitachi).

MfG Peter(TOO)

Es sit ja tatsächlich richtig, dass Du von MSVC/ Win ausgegangen bist. Darunter entwickle ich erstmal auch. Ich achte halt nur darauf, dass ich ansi c einhalte (bis auf ein paar entfernbare Nettigkeiten =), damit es ohne Weiteres auf dem MC-Board läuft. Für das Bord ist dann ein gcc-compiler zuständig.
Bei unserem Schnack wurde es halt nur Zeit zu erwähnen, dass Win nicht die Zielplattform sein wird.
Leider habe ich nicht den Luxus, für das BS zuständig zu sein =) Vielmehr macht mir aber die Tatsache sorgen, dass ich in den Eigenheiten von C nicht geübt bin. Programmieren ist halt nur ~Programmieren…