Anzeige:
Ergebnis 1 bis 10 von 10

Thema: Speicherleck bei realloc()?

  1. #1
    Registrierter Benutzer
    Registriert seit
    02.01.2005
    Beiträge
    53

    Speicherleck bei realloc()?

    Ich lese die Namen der momentan bekannten Netzwerkschnittstellen aus /proc/net/dev mit einem kleinen C-Programm aus. Das Programm holt sich eine Zeile mittels readline(), dann untersuche ich, wo der Name der Schnittstelle anfängt (ifname_start) und wo er endet (ifname_end).

    Die Namen sollen am Ende in einer char**-Struktur (char **ifnames) sein, die ich der Funktion übergebe. Weil ich natürlich nicht weiß, wie viele Namen im System bekannt sind und wie lang die sind, muß die Struktur dynamisch bei jedem Zeilendurchgang wachsen, was ich mittels realloc() implementiere.

    Der relevante Codeabschnitt sieht dann so aus:

    Code:
    void get_ifnames (char *proc_net_dev_path, struct interfaces *ifs){
        /* Prepare return array. */
        char **ifnames = 0;
    
        /* Try to open file. */
        
        FILE *fp = fopen (proc_net_dev_path, "r");
        if (fp == NULL) {
            /* Something went wrong. Print an error to stderr and return null. */
            fprintf (stderr, "ERROR: Could not open %s to read interfaces names.\n",
                proc_net_dev_path);
            return;
        }
    
        /*
            Read line by line from the file. We can throw away the first two lines
            as these are table headers. After that, we read what we get until the
            first double colon ":", because this is the interface name, and throw
            away the spaces.
        */
    
        /* Count how many interfaces we've got. */
        int iface_count = 0;
    
        /* Line to read into. Set to NULL to let getline allocate the memory. */
        char *line = 0;
    
        /* How many bytes we've read. */
        ssize_t read;
    
        /* The size of the line. */
        size_t len = 0;
    
        /* Throw away the first two lines, these are only headers. */
        read = getline (&line, &len, fp); free (line); line = 0;
        read = getline (&line, &len, fp); free (line); line = 0;
    
        /* Now we read what's left over. */
        while ((read = getline (&line, &len, fp)) != -1) {
            /*
                First thing to do: Read until the first double colon, throw away
                spaces, and remeber start and end positions.
            */
            int ifname_start = 0;
            int ifname_end = 0;
            for (size_t i = 0; i != strlen (line); ++i) {
                if (line[i] == '\0' || line[i] == ':') break;
                if (line[i] == ' ') ++ifname_start;
                ++ifname_end;
            }
    
            /* Resize array, add new interface name. */
            
            ++iface_count;
            char **temp =
    		realloc (ifnames, iface_count * sizeof (char*));
            if (!temp) break;
    	else ifnames = temp;
    
            /* Add new interface name */
    
            char **p = ifnames + iface_count - 1;
            *p = malloc (sizeof (char) * (ifname_end-ifname_start));
            strncpy (*p, (line+ifname_start), ifname_end-ifname_start);
    
            free (line); line = 0;
        }
    
        ifs->names = ifnames;
        ifs->num = iface_count;
    }
    Das funktioniert auch prima. Aber wenn ich Valgrind auf mein Programm ansetze, meldet es ein Speicherleck an der Stelle, an der ich "realloc()" ausführe (Im Quelltext fett markiert). Warum? realloc() vergrößert doch den Speicherbereich, ohne etwas wegzuwerfen? Wenn ich den Zeiger auf den alten Speicherbereich behalte und mit free() versuche, was zu löschen, dann sind meine Daten - logischerweise - immer weg. Ich stehe richtig auf dem Schlauch und habe keinerlei Ahnung, wie ich das Speicherleck beseitigen könnte.

    Valgrind meldet auf jeden Fall folgendes:

    Code:
    ==1934== 24 (20 direct, 4 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 4
    ==1934==    at 0x40204FB: realloc (vg_replace_malloc.c:306)
    ==1934==    by 0x8048633: get_ifnames (broadcastexample.c:83)
    ==1934==    by 0x804870D: main (broadcastexample.c:104)
    "broadcastexample.c:83" ist genau der realloc()-Aufruf. *grummel*

    Hat jemand eine Idee?

  2. #2
    Registrierter Benutzer Avatar von Detrius
    Registriert seit
    09.03.2004
    Ort
    Altena
    Beiträge
    64
    Code:
    ifnames = (char **) realloc (ifnames, iface_count * sizeof (char*));
    Damit sollte valgrind zufriedengestellt sein. Zumindest hat das der kleine Test hier ergeben. :-)

  3. #3
    Registrierter Benutzer
    Registriert seit
    02.01.2005
    Beiträge
    53
    Sollte das nicht automatisch typkonvertieren? Anscheinend nicht... *seufz* Immer sind's die Kleinigkeiten.

    Danke.

  4. #4
    Registrierter Benutzer
    Registriert seit
    02.01.2005
    Beiträge
    53
    Das beschäftigt mich jetzt noch weiter. Wenn ich die explizite Typkonvertierung weglasse, müßte doch, sofern Valgrinds Meldung kein "false positive" ist, anderer Assember-Code rauskommen, oder?

    Wenn ich mittels "gcc -S" übersetzen lasse und mir dann die erzeugten Dateien ansehe bzw. sie mit "diff" vergleiche, erhalte ich folgende Ausgabe:

    Code:
    --- broadcastexample.s  2007-05-14 15:33:57.000000000 +0200
    +++ broadcastexample-withoutcast.s      2007-05-14 15:34:02.000000000 +0200
    @@ -1,4 +1,4 @@
    -       .file   "broadcastexample.c"
    +       .file   "broadcastexample-withoutcast.c"
            .section        .debug_abbrev,"",@progbits
     .Ldebug_abbrev0:
            .section        .debug_info,"",@progbits
    @@ -11,7 +11,7 @@
            .type   send_broadcast, @function
     send_broadcast:
     .LFB42:
    -       .file 1 "broadcastexample.c"
    +       .file 1 "broadcastexample-withoutcast.c"
            .loc 1 112 0
     .LVL0:
            pushl   %ebp
    @@ -1995,8 +1995,6 @@
            .string "iface_count"
     .LASF27:
            .string "_IO_save_end"
    -.LASF76:
    -       .string "broadcastexample.c"
     .LASF38:
            .string "__pad1"
     .LASF39:
    @@ -2023,5 +2021,7 @@
            .string "get_ifnames"
     .LASF20:
            .string "_IO_write_base"
    +.LASF76:
    +       .string "broadcastexample-withoutcast.c"
            .ident  "GCC: (GNU) 4.1.1 20070105 (Red Hat 4.1.1-51)"
            .section        .note.GNU-stack,"",@progbits
    ... was für mich so aussieht, als würde sich beim Assembler-Code nichts ändern bis auf den Dateinamen. Das heißt: Eigentlich ist der Speicherbereich immer genauso groß, nur Valgrind meckert bei der einen Version und bei der anderen nicht. Wo liegt da der Unterschied, der so signifikant ist, daß Valgrind "definitely lost" meldet?

  5. #5
    Registrierter Benutzer Avatar von Detrius
    Registriert seit
    09.03.2004
    Ort
    Altena
    Beiträge
    64
    Es liegt nicht an der expliziten Typkonvertierung von void * nach char **, sondern daran, was links vom Gleichheitszeichen steht. Wenn das die gleiche Variable ist wie die, die durch realloc erweitert wird, dann ist valgrind zufrieden. Ist es aber eine andere, wie bei Deinem ersten Beispiel, dann wird ein Speicherleck erkannt.

    Zumindest hat es sich hier so verhalten.

  6. #6
    Registrierter Benutzer
    Registriert seit
    02.01.2005
    Beiträge
    53
    Bei mir nicht. Valgrind ist auch dann zufrieden, wenn ich *nur* die explizite Typkonvertierung einführe. Die temporäre Variable habe ich überhaupt erst wegen Valgrind eingeführt (schließlich kann realloc() 0 zurückliefern, wenn's irgendein Problem gibt; ich dachte, das schmeckt Valgrind nicht) - deswegen werde ich ja gerade stutzig.

  7. #7
    Registrierter Benutzer Avatar von Detrius
    Registriert seit
    09.03.2004
    Ort
    Altena
    Beiträge
    64
    Hmm, man muss nur mal darauf achten welche Variable man freigibt.

    Hier mal ein kleines Beispiel, bei dem valgrind --leak-check=full nichts zu beanstanden hat.

    Code:
    #include <stdlib.h>
    
    int main()
    {
            char **foo1 = NULL;
            char **foo2 = NULL;
            char **foo3 = NULL;
            char **foo4 = NULL;
    
            char **bar = realloc(foo1, 10 * sizeof(char *));
            free(bar);
    
            char **baz = (char **) realloc(foo2, 10 * sizeof(char *));
            free(baz);
    
            foo3 = (char **) realloc(foo3, 10 * sizeof(char *));
            free(foo3);
    
            foo4 = realloc(foo4, 10 * sizeof(char *));
            free(foo4);
    
            return 0;
    }

  8. #8
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Moin,

    man sollte IMHO bei _allen_ *alloc-Funktionen casten. Wenn man sich die Deklarationen von malloc, calloc und realloc mal anguckt, dann sieht man nämlich, dass sie alle void* zurückliefern. Es ist kein Wunder, wenn valgrind rummault bei nicht gecasteten Aufrufen - dann weiss es nämlich nicht, welche Art von Pointer zurückgeliefert werden soll. Du als Programmierer hast da vielleicht ein char* oder char** im Sinn - aber die Argumente für die *alloc's sind einfach nur size_t - und das ist nun mal nur eine Zahl, die nichts über den zu liefernden Datentp aussagt. Ohne Typecasting ist nicht klar, wie das Ergebnis aussehen soll. Ein char* ist eine Adresse, ein long* auch. Das Alignment ist aber immer abhängig vom Datentyp. Ein Byte- oder char-Zeiger wird u. U. anders ausgerichtet als ein long- oder long64-Pointer.

    Jan

  9. #9
    Registrierter Benutzer
    Registriert seit
    02.01.2005
    Beiträge
    53
    D.h. ohne explizite Typkonvertierung könnte ich beispielweise auf PPC Probleme kriegen, oder über verschiedene Größenimplementierungen bei verschiedenen Kompilern stolpern, habe ich das richtig verstanden?

  10. #10
    Registrierter Benutzer
    Registriert seit
    07.05.2007
    Beiträge
    656
    Zitat Zitat von Technaton Beitrag anzeigen
    D.h. ohne explizite Typkonvertierung könnte ich beispielweise auf PPC Probleme kriegen, oder über verschiedene Größenimplementierungen bei verschiedenen Kompilern stolpern, habe ich das richtig verstanden?
    Nein, ich meine damit, dass Du einen void* von den Funktionen kriegst - völlig unabhängig von verschiedenen Plattformen / Compilern ... void* ist unbestimmt, das Alignment im Speicher wird erst durch den cast klar. Selbst wenn der Compiler nur warnings ausspuckt - es ist schlicht und einfach sauberer Programmierstil, per cast anzugeben, was man erwartet.

    Jan

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •