Free Pointer



  • Vielen Dank erstmal für die ganzen zügigen Antworten 🙂
    Mein Code war sehr schnell heruntergetippt und entsprechend habe ich nicht darauf geachtet, dass ich den Zeiger oben mit der Anweisung

    zeiger = "";
    

    natürlich überschreibe. Vielen Dank erstmal für den Hinweis, allerdings verstehe ich die Anmerkung über die Zeigerarithmetik nicht.

    Ich habe folgenden Code gerade ausprobiert und er gibt mir auf jeden Fall das 'e' aus, wie ich es mir gedacht habe (Windows 7 64 Bit, compiliert mit GNU-GCC-Compiler (MinGW) in der neuesten Version):

    #include <stdio.h>
    #include <stdlib.h>
    
    #include <string.h>
    
    int main()
    {
        char* zeiger = (char*) malloc(10 * sizeof(char));
        strcpy(zeiger, "Test"); // *zeiger == 'T'
        zeiger += sizeof(char); // *zeiger == 'e'
    
        printf("%c",*zeiger);
    
        free(zeiger);
    
        return 0;
    }
    

    Ausgabe: e

    Wenn ich das richtig verstanden habe, muss der Zeiger also auf den Anfangsbereich zeigen (@DirkB) und entsprechend muss ich die Operationen über einen anderen zeiger lösen oder?

    LG,

    Datenstrom

    EDIT:
    Da free scheinbar über keinen Rückgabewert verfügt und den Wert von *ptr auf nicht zu verändern scheint, weiß ich nicht, wie ich überprüfen soll, ob der gesamte Speicherbereich durch den oberen Code nun wirklich freigegeben wurde oder nicht.
    Wie kann ich das überprüfen?


  • Mod

    Das mit dem +sizeof(char) funktioniert hier, weil sizeof(char) == 1 ist. Bei Zeigern gilt allgemein, dass sie intelligent rechnen. Wenn du also einen int* foo; hast, dann ist foo + 1 der darauf folgende int. Du musst gar nicht wissen, wie groß ein int ist. Im Gegenteil, wenn du foo + sizeof(int) schreibst, wirst du den 4. oder gar 8. int bekommen, der auf foo folgt, da sizeof(int) i.d.R. etwas wie 4 oder 8 ist. Diese Verhalten ist es gerade, das man mit dem Wort Ziegerarithmetik beschreibt.



  • Das ist eine unheimlich hilfreiche Information SeppJ. Vielen Dank dafür, da habe ich in dem Fall wirklich einfach nur Glück gehabt mit der char-Größe (hab es gerade mit Int ausprobiert und du hast Recht) 🙂

    Auch das Casten vom malloc-Pointer zum (char 😉 scheint überflüssig zu sein (wenn ich es weglasse, meckert der Compiler nicht und das Ergebnis ist das Gleiche) -> vielen Dank "aptr".

    Wenn ich euch nun richtig verstanden habe, wird der Speicherbereich nicht durch das free wieder freigegeben, da ich den Zeiger auf den Speicherbereich verschoben habe richtig?


  • Mod

    Datenstrom schrieb:

    Wenn ich euch nun richtig verstanden habe, wird der Speicherbereich nicht durch das free wieder freigegeben, da ich den Zeiger auf den Speicherbereich verschoben habe richtig?

    Nicht "nur" das. Es ist undefiniertes Verhalten, einen falschen Pointer (außer NULL) freizugeben und wird in der Regel mit einem Absturz quittiert.

    Datenzeiger sind in C implizit in void* und umgekehrt konvertierbar. Der Cast bei malloc wäre etwas, was nur bei C++ nötig wäre.



  • Alles klar. Vielen lieben Dank an alle, die Informationen waren wirklich super hilfreich. Der folgende Code sollte den Speicher richtig allokieren und auch wieder freigeben:

    #include <stdio.h>
    #include <stdlib.h>
    
    #include <string.h>
    
    int main()
    {
        char* zeiger = malloc(10);
        char* zeiger2 = zeiger;
    
        strcpy(zeiger, "Test");
        zeiger2 += 1;
    
        printf("%c",*zeiger2); // = 'e'
    
        free(zeiger);
    
        return 0;
    }
    

    Da zeiger2 keinen eigenen Speicherbereich durch malloc zugewiesen bekommen hat, darf dieser auch nicht extra durch free wieder gesäubert werden. Sicherheitshalber kann man zeiger2 noch NULL setzen (muss man aber nicht) -> richtig verstanden?

    Wäre nur noch eine Frage zu klären (hatte ich in einem Edit, den du aber wahrscheinlich nicht mehr gelesen hast):

    Da free ja keinen Rückgabewert hat und den Zeigerinhalt auch nicht verändert, weiß ich gar nicht, wie ich wirklich überprüfen kann, ob der gesamte Speicherbereich auch freigegeben wurde. Gibt es da eine einfache Variante, die das Programm nicht zum Absturz bringt?



  • Datenstrom schrieb:

    Da free ja keinen Rückgabewert hat und den Zeigerinhalt auch nicht verändert, weiß ich gar nicht, wie ich wirklich überprüfen kann, ob der gesamte Speicherbereich auch freigegeben wurde. Gibt es da eine einfache Variante, die das Programm nicht zum Absturz bringt?

    Nein, es gibt keine Möglichkeit, eine Freigabe innerhalb eines Programmes zu überprüfen.

    Den Absturz hast du übrigens nur unter Windows, andere Betriebssysteme sind da weniger pingelig - es gibt zwar nicht unbedingt sofort einen Absturz, es kann aber nicht freigegebener Speicher zurückbleiben.

    Btw: wofür willst du überprüfen, ob der Speicher freigegeben wurde? Selbst wenn es so einen Rückgabewert gibt und dein Programm merkt irgend wann mal "hey, der Speicher wurde eben ja gar nicht freigegeben" - was willst du dann programmatisch machen?



  • Das freigeben heißt nichts anderes, als das er für das Programm nicht mehr zur Verfügung steht.
    Das kannst du nicht überprüfen, das musst du wissen.
    Am besten du setzt den Zeiger danach auf NULL.

    free(zeiger);
        zeiger  = NULL;  // ist hier kurz vor dem Ende natürlich nicht nötig.
        zeiger2 = NULL;  // das zeiger2 in den Bereich von zeiger verweist, weist du als Programmierer.
    
        return 0;
    }
    


  • Dabei ging es mir nur um die Möglichkeit einen Denkfehler sichtbar zu machen. Wenn ich zum Beispiel im Programm meines ersten Postings die Möglichkeit der erfolgreichen Free-Abfrage hätte, wüsste ich auf jeden Fall, dass ich etwas falsch mache und könnte dann genauer nach Fehlern suchen.

    Nungut, so muss ich den Code einfach doppelt überprüfen, um Fehler auszuschließen.

    Mir ist gerade aufgefallen, dass ich das free nicht nur durch einen zweiten Zeiger lösen könnte, sondern auch folgendermaßen:

    #include <stdio.h>
    #include <stdlib.h>
    
    #include <string.h>
    
    int main()
    {
        char* zeiger = malloc(10);
    
        strcpy(zeiger, "Test");
        zeiger += 1;
    
        printf("%c",*zeiger);
    
        zeiger -= 1; 
        free(zeiger);
    
        return 0;
    }
    

    Durch das zeiger -= 1 sorge ich dafür, dass der Zeiger wieder dort hinzeigt, wo er ursprünglich auch hinzeigen sollte und das dürfte dann funktionieren. Genau das wollte ich durch mein erstes Posting herausfinden (der Zeiger darf bei einem free also nicht von seiner ursprünglichen Position verschoben worden sein).

    LG,

    Datenstrom



  • Nimm trotzdem einen zweiten Zeiger. Damit bist du auf der sicheren Seite.

    Du sparst mit dieser Rumrechnerei weder Speicher noch Geschwindigkeit.
    Du hast nur eine Fehlerquelle mehr im Programm.

    Statt zeiger += 1; nimmt man meist ++zeiger oder auch zeiger++ je nach Anwendungsfall.

    Z.B.:

    printf("%c",*++zeiger);
    


  • Wenn du malloc aufrufst, sieht der Speicher in etwas so aus:

    +------------------------------+
                   | Grösse des Speicherbereichs  |
                   +------------------------------+
    Zeiger ------> |  1. Byte zum freien Gebrauch |
                   |  2. Byte zum freien Gebrauch |
                   |  3. Byte zum freien Gebrauch |
                   |  4. Byte zum freien Gebrauch |
                   |  5. Byte zum freien Gebrauch |
                   |  6. Byte zum freien Gebrauch |
                   |  7. Byte zum freien Gebrauch |
                   |  8. Byte zum freien Gebrauch |
                   |  9. Byte zum freien Gebrauch |
                   | 10. Byte zum freien Gebrauch |
                   +------------------------------+
    

    Wenn du wieder free() aufrufst, wird der Zeiger dekrementiert und die Grösse des Speicherbereichs ausgelesen. So viele Bytes werden dann vom Speicher gelöscht.

    Wenn du free() aufrufst, der Zeiger aber auf das 2. Byte zeigt, wird das 1. Byte als Grösse des Speicherbereichs interpretiert, was zu seltsamen Fehlern führt.

    Daher musst du immer den Zeiger zum Beginn des Datenblockes übergeben.


  • Mod

    Du kannst übrigens auch free(zeiger -1); machen. Ich würde dir trotzdem empfehlen, das einfach über eine Kopie zu machen, das ist viel sauberer und du verlierst nicht so leicht den Überblick.

    Zum Thema Nullsetzen: Was soll das hier bringen? Wenn du diese Frage beantworten kannst, dann tu es. Ansonsten nicht, denn dadurch verschleppst du bloß Fehler. Diese Frage sollte auch DirkB mal beantworten. Dieses Nullsetzen ist etwas, was Leute tun, die mal den Code für eine komplexe Datenstruktur wie eine Liste gesehen, aber nicht verstanden haben. Oder Leute die von solchen Leuten unterrichtet wurden. Oder von Leuten unterrichtet wurden, die von solchen Leuten unterrichtet wurden 🙂 .
    Du musst dein Programm so programmieren, dass gar nichts schiefgehen kann. Wenn du deine Zeiger auf Null setzt, weil die Möglichkeit besteht, dass dein Programm eventuell falsche Zeiger freigibt, dann ist dies nicht die Lösung, sondern du musst die Programmlogik korrigieren. Mit nullgesetzten Zeigern bemerkst du den Fehler nicht einmal (außer natürlich in der Präsentation beim Chef, wo es dann auf einmal abstürzt, weil es auf einer anderen Maschine läuft). Ja, in C ohne deterministische Destruktoren ist es schwer (oder besser gesagt: anstrengend), wirklich narrensicher zu programmieren. Eine gute Faustregel:
    Niemals Verantwortung übertragen. Wer Speicher reserviert, gibt ihn auch wieder frei. Das heißt, keine ge-malloc-ten Pointer aus Funktionen zurück geben. Wenn Speicher über Funktionen hinweg übergeben werden muss, übergibt man einen Handler, auf den später ein Destruktor aufgerufen werden muss von der Funktion, die den Handler erzeugt hat (siehe auch wieder den erste Satz in diesem Absatz dazu).

    Die Korrektheit eines Programmes kannst du unter Linux relativ gut durch valgrind prüfen. Dieses findet sehr zuverlässig ziemlich viele Arten von Speicherlöchern (und auch noch allerlei andere Fehler wie z.B. uninitialisierte Variablen).



  • Klasse, nun sind alle Fragen gelöst.
    Vielen lieben Dank für eure Zeit und eure Mühen!

    Das Nullsetzen selbst ist natürlich nicht notwendig. Allerdings kann man das machen, wenn man verhindern möchte, dass man irgendwann auf genau diesen Zeiger zurückgreift, dessen Inhalt durch die Freigabe inzwischen alles Mögliche sein könnte.
    Wenn er allerdings Null gesetzt wird, kann man bei einem Zugriff recht schnell feststellen, dass man nicht auf diesen Zeiger zurückgreifen soll -> ist also nur eine Art Absicherung, die man aber gerade bei kleinen Projekten nicht braucht.

    Vielen lieben Dank Leute, ihr habt mich vor Speicher-Lecks bewahrt 🙂

    Datenstrom



  • SeppJ schrieb:

    Du musst dein Programm so programmieren, dass gar nichts schiefgehen kann.

    Oh ja. Weise Worte.

    Nur dummerweise sitzt der Bugger vor dem Computer (und hat keinen Plan)
    Wenn man Fehler sucht hilft schon mal ein Zeiger der komischerweise auf NULL verweist.



  • Ich halte auch nichts davon, im Produktionscode irgendwas fürs Debugging reinzufrickeln und gehe hier mit SeppJ konform, dass dies nur zur Fehlerverschleierung führt.
    Beim free-Thema nutzt man so die Eigenschaft mancher Compilercodes nicht aus, bei mehrfachem free für die gleiche Adresse eine Exception zu werfen.


  • Mod

    Noch ein Nachtrag: Da free eines Nullzeigers ein definiertes Verhalten (nämlich nichts) auslöst, lässt sich solch ein fehlerhaftes free auch nur schwer automatisiert finden, da es ja eigentlich kein Fehler im eigentlichen Sinne mehr ist, während ein free eines falschen Zeigers i.d.R. sofort knallt oder spätestens von Tools wie valgrind gefunden wird.
    Der umgekehrte Fall, dass ein Zugriff auf einen ungültigen Zeiger manchmal funktioniert, aber durch Nullsetzen des Zeigers trotzdem funktioniert, ist ebenfalls durch Tools wie valgrind sehr leicht zu finden, auch wenn man den Zeiger nicht auf 0 setzt.

    Man macht also effektiv den Programmierhilfsprogrammen die Arbeit schwer, gewinnt aber eigentlich nichts, denn man verschiebt nur wie ein Fehler sich äußerst auf einen anderen Fall (von Absturz bei doppeltem free auf Absturz bei Dereferenzierung).



  • SeppJ schrieb:

    Der umgekehrte Fall, dass ein Zugriff auf einen ungültigen Zeiger manchmal funktioniert, aber durch Nullsetzen des Zeigers trotzdem funktioniert, ist ebenfalls durch Tools wie valgrind sehr leicht zu finden, auch wenn man den Zeiger nicht auf 0 setzt.

    Wobei das NULL-setzen hierbei explizit helfen würde, da eine anschließende Dereferenzierung nicht mehr "manchmal funktioniert", sondern auch ohne valgrind durch die meisten Compilercodes abbrechen würde, man also den Fehler "immer" gemeldet bekommt.



  • SeppJ schrieb:

    Zum Thema Nullsetzen: Was soll das hier bringen? Wenn du diese Frage beantworten kannst, dann tu es. Ansonsten nicht, denn dadurch verschleppst du bloß Fehler. Diese Frage sollte auch DirkB mal beantworten. Dieses Nullsetzen ist etwas, was Leute tun, die mal den Code für eine komplexe Datenstruktur wie eine Liste gesehen, aber nicht verstanden haben.

    Da ist ja wieder die Großkotzigkeit. Das scheint bei dir pathologisch zu sein.

    SeppJ schrieb:

    Du musst dein Programm so programmieren, dass gar nichts schiefgehen kann.

    Ach ja, die ganze Welt bemüht sich erfolgos, aber SeppJ hat es geschafft: er programmiert 100% fehlerfrei. Toll!

    Aber beim Thema "fehlertolerante und robuste Programme" ist das Nullsetzen so eines Zeigers definitiv eine Möglichkeit:

    1. Ein free() wird nur ausgeführt, wenn der Zeiger !=NULL ist
    2. Nach dem free() wird der Zeiger auf NULL gesetzt
    3. Vor dem free() wird ein assert gesetzt, der überprüft, ob der Zeiger ungleich NULL ist

    Im Ergebnis hat man ein Programm, dass auch bei versehentlich doppelt freigegebenen Speicherbereichen eben nicht rumzickt sondern sauber und vor allem problemlos weiterarbeitet. Und in der Debug-Version fliegt einem die Stelle um die Ohren, so dass man gleich sieht, wo was schief geht. Das ist insbesondere für solche Fälle interessant, die man nicht erwartet, weil sie eben nur in bestimmten, sehr seltenen Fällen auftreten oder in Zusammenhang mit nicht vorhersehbaren Kombinationen äußerer Umstände.

    Das braucht man nur dann nicht tun, wenn man Programme schreibt, die so trivial sind, dass man alle Abläufe und Abhängigkeiten zu 100% überblickt - oder wenn man als einziger Mensch auf diesem Planeten mit der Genialität geschlagen ist, immer und unter allen Bedingungen fehlerfrei programmieren zu können und jederzeit alle Gesamtzusammenhänge auch komplexerer Systeme zu überblicken.


  • Mod

    Würg schrieb:

    Aber beim Thema "fehlertolerante und robuste Programme" ist das Nullsetzen so eines Zeigers definitiv eine Möglichkeit:

    1. Ein free() wird nur ausgeführt, wenn der Zeiger !=NULL ist
    2. Nach dem free() wird der Zeiger auf NULL gesetzt
    3. Vor dem free() wird ein assert gesetzt, der überprüft, ob der Zeiger ungleich NULL ist

    Im Ergebnis hat man ein Programm, dass auch bei versehentlich doppelt freigegebenen Speicherbereichen eben nicht rumzickt sondern sauber und vor allem problemlos weiterarbeitet.

    Du solltest dir mein Großgekotze vielleicht mal durchlesen, bevor du solchen Blödsinn verzapfst, der ganz genau bestätigt, warum das Nullsetzen eine schlechte Idee ist. Es arbeitet eben nicht problemlos weiter. Ein Fehler ist aufgetreten. Er wurde vertuscht. Das Programm ist nun in einem undefinierten Zustand und läuft trotzdem einfach weiter. Wenn der nächste Folgefehler auftritt, wird alles noch viel schlimmer.

    Ach ja, die ganze Welt bemüht sich erfolgos, aber SeppJ hat es geschafft: er programmiert 100% fehlerfrei. Toll!

    Naja, ich mach's halt nicht in C, weil mir das zu anstrengend wäre. In C++ ist das wesentlich einfacher. Und ja, meine Programme sind 100% fehlerfrei. Ist nicht schwer. Solltest du mal versuchen, anstatt an alten Pseudogutpraktiken festzuhalten.

    Das braucht man nur dann nicht tun, wenn man Programme schreibt, die so trivial sind, dass man alle Abläufe und Abhängigkeiten zu 100% überblickt - oder wenn man als einziger Mensch auf diesem Planeten mit der Genialität geschlagen ist, immer und unter allen Bedingungen fehlerfrei programmieren zu können und jederzeit alle Gesamtzusammenhänge auch komplexerer Systeme zu überblicken.

    Das hat nichts mit Genialität oder Überblick über komplexe Systeme zu tun, das sind ganz grundlegende Programmiertechniken, an die man sich eben halten muss, dann kann gar nichts schiefgehen. Dein Stuss hier zeigt, dass du diese nicht kennst, aber trotzdem mal groß rummotzen möchtest, weil du mich nicht leiden kannst.



  • SeppJ schrieb:

    ...das Nullsetzen eine schlechte Idee ist. Es arbeitet eben nicht problemlos weiter. Ein Fehler ist aufgetreten. Er wurde vertuscht. Das Programm ist nun in einem undefinierten Zustand und läuft trotzdem einfach weiter.

    Wo bitte siehst du einen undefinierten Zustand? Wenn der Pointer !=NULL ist, dann wird free() ausgeführt. Wenn er ==NULL ist, eben nicht. NULL ist er immer nur dann, wenn er noch nicht benutzt wurde, oder eben schon freigegeben. Kein undefinierter Zustand, kein doppeltes Freigeben des gleichen Speichers, kein Freigeben von NULL, nichts. Die Release-Version arbeitet problemlos weiter ohne den Benutzer mit unsinnigen Fehlermeldungen zu nerven und die Debug-Version macht den Entwickler auf das Problem aufmerksam.

    SeppJ schrieb:

    Wenn der nächste Folgefehler auftritt, wird alles noch viel schlimmer.

    Was wird wo schlimmer?

    Und ja, meine Programme sind 100% fehlerfrei.

    Macht dich dieser Größenwahn im RL eigentlich sehr einsam?


  • Mod

    Würg schrieb:

    SeppJ schrieb:

    ...das Nullsetzen eine schlechte Idee ist. Es arbeitet eben nicht problemlos weiter. Ein Fehler ist aufgetreten. Er wurde vertuscht. Das Programm ist nun in einem undefinierten Zustand und läuft trotzdem einfach weiter.

    Wo bitte siehst du einen undefinierten Zustand? Wenn der Pointer !=NULL ist, dann wird free() ausgeführt. Wenn er ==NULL ist, eben nicht. NULL ist er immer nur dann, wenn er noch nicht benutzt wurde, oder eben schon freigegeben. Kein undefinierter Zustand, kein doppeltes Freigeben des gleichen Speichers, kein Freigeben von NULL, nichts. Die Release-Version arbeitet problemlos weiter ohne den Benutzer mit unsinnigen Fehlermeldungen zu nerven und die Debug-Version macht den Entwickler auf das Problem aufmerksam.

    Das Programm hat einen Logikfehler, sonst hätte es den Pointer gar nicht zweimal freigegeben. Das ist undefiniert wie es nur sein kann.

    Wenn du auf den Fehler irgendwie sauber reagieren würdest (z.B. das C-Äquivalent einer Exception), dann könnte das Programm trotz des Fehlers in einem definierten Zustand bleiben (wenn die überliegenden Konstrukte ine entsprechende Ausnahmegarantie abgeben). Dein Vorschlag ist jedoch "on error resume next", etwas was Zwölfjährige bei ersten Schritten mit BASIC für eine gute Idee halten (ich gehörte übrigens auch zu dieser Gruppe).

    SeppJ schrieb:

    Wenn der nächste Folgefehler auftritt, wird alles noch viel schlimmer.

    Was wird wo schlimmer?

    Dein Programm ist in einem undefinierten Zustand. Es bleibt bestenfalls gleichschlimm, wird sich aber nicht erholen. Genausogut könntest du bei Integerdivision durch 0 einfach weitermachen oder wenn du feststellst, dass assert(1 + 1 == 2) fehlschlägt. Es ist ein Logikfehler, da gibt es keine sinnvolle Reaktion drauf, außer versuchen alles wieder so herzurichten wie es vorher war. Auf gar keinen Fall aber einfach weitermachen.

    Und ja, meine Programme sind 100% fehlerfrei.

    Macht dich dieser Größenwahn im RL eigentlich sehr einsam?

    Du merkst anscheinend nicht einmal, dass ich die Wahrheit spreche, da du dir gar nicht vorstellen kannst, dass man fehlerfrei programmieren kann. Kein Wunder bei deiner Einstellung.


Anmelden zum Antworten