Free Pointer



  • Ich würde nicht strcpy verwenden wie von DirkB vorgeschlagen sondern strdup.

    char* string = strdup("Test");
    char *zeiger = string; // *zeiger == 'T'
    ++zeiger; // *zeiger == 'e'
    free(string);
    

    PS: Man castet den Rückgabewert von malloc nicht!



  • aptr schrieb:

    Ich würde nicht strcpy verwenden wie von DirkB vorgeschlagen sondern strdup.

    char* string = strdup("Test");
    char *zeiger = string; // *zeiger == 'T'
    ++zeiger; // *zeiger == 'e'
    free(string);
    

    PS: Man castet den Rückgabewert von malloc nicht!

    Man wechselt auch nicht stilistisch von char* zu char *.



  • aptr schrieb:

    Ich würde nicht strcpy verwenden wie von DirkB vorgeschlagen sondern strdup.

    Das ist aber nicht das Gleiche, da strdup hier nur 5 Byte belegt und nicht 10.
    Zudem hat es strdup immer noch nicht in den Standard geschafft.



  • SeppJ schrieb:

    Wie du leicht durch ausprobieren hättest herausfinden können...

    ...und überhaupt scheint mir bei einem Ausdruck wie zeiger += sizeof(char); , dass du Zeigerarithmetik nicht verstanden hast...

    Bei dir sitzen anscheinend auch die Grundlagen von Zeichenketten nicht.

    Immer wieder interessant zu sehen, wie sich manche hier gebärden. Da stellt jemand, der ganz offensichtlich Anfänger ist, eine Frage und bekommt als Antwort in einer Tour reingewürgt, wie doof er doch eigentlich ist.

    Was soll das? Fühlst du dich dadurch irgend wie besser? Brauchst du das für dein Selbstwertgefühl oder hapert es ganz einfach nur grundlegend an deinen zwischenmenschlichen Umgangsformen?



  • 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.


Anmelden zum Antworten