PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Speicherleck bei realloc()?



Technaton
13-05-2007, 21:23
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:



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:



==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?

Detrius
14-05-2007, 00:02
ifnames = (char **) realloc (ifnames, iface_count * sizeof (char*));
Damit sollte valgrind zufriedengestellt sein. Zumindest hat das der kleine Test hier ergeben. :-)

Technaton
14-05-2007, 11:09
Sollte das nicht automatisch typkonvertieren? Anscheinend nicht... *seufz* Immer sind's die Kleinigkeiten.

Danke. :)

Technaton
14-05-2007, 17:10
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:


--- 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?

Detrius
14-05-2007, 19:27
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. :)

Technaton
14-05-2007, 20:03
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.

Detrius
14-05-2007, 21:01
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.


#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;
}

jan61
14-05-2007, 23:30
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

Technaton
15-05-2007, 09:11
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?

jan61
16-05-2007, 22:26
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