Freigabe des Speichers mit free() führt zu Zugriffsfehler auf dem Heap
-
Hallo,
ich über mich mit dem Buch "C/C++, das umfassende Lehrbuch" von Kaiser/Kecher. Momentan beschäftige ich mich mit dynamischen Datenstrukturen. Die Übungsaufgabe lautet, aus einer Textdatei den Namen und die Groesse eines Dinosaueriers auszulesen und in ein dynamisch erstelltes Array von Pointern auf die entsprechende Struktur eines Dinos zu speichern (und anschließend zu sortieren, dass soll hier aber der Übersichtlichkeit her gespart werden). In der Textdatei steht in der ersten Zeile die Anzahl der Dinos, in der zweiten bis elften jeweils der Name des Dinos, gefolt von einem Leerzeichen und seiner Größe.
10
Tyrannosaurus 12
Euoplocephalus 6
Triceratops 7
Pachycephalosaurus 6
Shantungosaurus 15
Ornithominus 4
Deinonychus 3
Iguanodon 9
Stegosaurus 6
Brachiosaurus 25Es funktioniert alles, ich kann die Daten auslesen und darauf zugreifen. Leider stürzt die abschließende Freigabe des Speichers immer ab.
#include <stdio.h> #include <stdlib.h> #include <string.h> struct dino { char *name; int groesse; }; struct dinodaten { struct dino **dinoarray; int anz; }; struct dino *lesedino(FILE *fp) { char buff[50]; struct dino *dino; dino = (struct dino*) malloc(sizeof(struct dino)); fscanf(fp, "%s %d", buff, &dino->groesse); dino->name = (char *) calloc(strlen(buff), sizeof(char)); strcpy(dino->name, buff); return (dino); } void dinoausgabe(struct dino **dinoarray, int i) { printf("%s, %d\n", dinoarray[i]->name, dinoarray[i]->groesse); } struct dinodaten *lesedatei() { FILE *fp; int anz; int i; struct dinodaten *dinosausdatei; fp = fopen("dinos.txt", "r"); fscanf(fp, "%d\n", &anz); dinosausdatei = (struct dinodaten *) malloc(sizeof(struct dinodaten)); dinosausdatei->dinoarray = (struct dino **) malloc(sizeof(struct dinodaten*)); for (i = 0; i < anz; i++) dinosausdatei->dinoarray[i]=lesedino(fp); dinosausdatei->anz = anz; fclose(fp); return dinosausdatei; } void dinodestructor(struct dinodaten *dinosausdatei) { int i; for (i = 0; i < dinosausdatei->anz; i++) { //Die Freigabe des Namensfeldes verursacht immer einen Zugriffsfehler. if (dinosausdatei->dinoarray[i]->name) free(dinosausdatei->dinoarray[i]->name); free(dinosausdatei->dinoarray[i]); } free(dinosausdatei->dinoarray); free(dinosausdatei); return; } int main() { int i; struct dinodaten *dinosausdatei; dinosausdatei = lesedatei(); while (1) { puts("Welchen Dino ausgeben?"); scanf("%d", &i); if (!i) break; dinoausgabe(dinosausdatei->dinoarray, i-1); } dinodestructor(dinosausdatei); return 1; }
Das gleiche Problem trat bereits bei der vorangegangenen Aufgabe auf, wo ich eine Que mithilfe eines Arrays erstellen sollte.
# include <stdio.h> # include <stdlib.h> /* ** Schnittstelle des abstrakten Datentyps Queue */ struct queue { int *array; int size; int top; int first; }; /* ** Implementierung des abstrakten Datentyps Queue */ struct queue *que_construct() { struct queue *que; que = (struct queue *)malloc( sizeof( struct queue)); if( !que) return 0; que->array = (int *) malloc(5* sizeof( int)); que->size = 100; que->top = 0; que->first = 0; return que; } int que_put( struct queue *que, int val) { if (que->top == que->size) que->array = (int *) realloc(que->array, (que->size + 5)*sizeof(int)); que->array[que->top++] = val; return 1; } int que_get( struct queue *que, int *val) { if( que->first == que->top) return 0; *val = que->array[que->first++]; return 1; } void que_destruct( struct queue *que) { int dummy; while( que_get( que, &dummy)) ; free(que->array); free( que); } /* ** Anwendung des abstrakten Datentyps Queue */ int main() { struct queue *que; int i; que = que_construct(); printf( "Put:"); for( i = 0; i < 10; i++) { printf( " %d", i); que_put( que, i); } printf( "\nGet:"); while( que_get( que, &i)) printf( " %d", i); printf( "\n"); que_destruct( que); }
Vielen Dank für Eure Hilfe!
-
1. Problem:
dino->name = (char *) calloc(strlen(buff), sizeof(char));
Weißt du was ein Stringterminator ist?
Jedenfalls braucht der auch Platz. Auch der Leerstring "" braucht Platz für ein Zeichen, obwohl strlen("") eine 0 liefert.2. Problem:
fscanf(fp, "%d\n", &anz); dinosausdatei = (struct dinodaten *) malloc(sizeof(struct dinodaten)); dinosausdatei->dinoarray = (struct dino **) malloc(sizeof(struct dinodaten*)); // Für wieviel Dinos ist Platz in deinem Speicher?
Für genau einen. Du musst da noch irgendwie die Anzahl (aus anz) mit verarbeiten.
~Weiter habe ich noch nicht gelesen.~
-
struct dino { char *name; int groesse; };
In Zeile 19 limitierst du die Anzahl an Zeichen für Dinonamen bereits auf 49 Charaktere + Terminierungszeichen, daher könntest es dem Namen in der Struktur gleich tun und dir die spätere Speicherbefreiung ersparen.
struct dinodaten { struct dino **dinoarray; int anz; };
Du musst einzelne dino Einträge nicht auch als dynamischen Speicher halten. Es reicht ein Zeiger auf einen Speicher, welcher alle Dino-Einträge sichern kann.
dino->name = (char *) calloc(strlen(buff), sizeof(char)); strcpy(dino->name, buff);
strlen gibt dir die Anzahl an Charakteren zurück, nicht aber das Terminierungszeichen! Demnach ist dino->name ein char zu klein, um buff aufzunehmen.
void dinoausgabe(struct dino **dinoarray, int i) { printf("%s, %d\n", dinoarray->name, dinoarray[i]->groesse); }
*Gefährlich, da bei ungültiger Eingabe in Zeile 85 hier ein illegaler Speicherzugriff erfolgen kann.
dinosausdatei = (struct dinodaten *) malloc(sizeof(struct dinodaten));
*dinodaten ist kein so großer Speicherbereich, dass Kopien teuer wären und sich das Übergeben des Speicherzeigers eher lohnt. Du kannst deine Dinoausgabe-Datei als normale Variable anlegen, dann sparst du dir später auch die Befreiung dynamischen Speichers.
dinosausdatei->dinoarray = (struct dino **) malloc(sizeof(struct dinodaten*));
Du reservierst hier Speicher für einen Zeiger zu einer dinodaten Struktur, was du aber eigentlich willst, ist Speicher für die Gesamtanzahl an Zeigern für dino Strukturen.
Mit Debugging kannst du dein Programm auf illegale Speicherzugriffe überprüfen.
-
DirkB schrieb:
1. Problem:
dino->name = (char *) calloc(strlen(buff), sizeof(char));
Weißt du was ein Stringterminator ist?
Jedenfalls braucht der auch Platz. Auch der Leerstring "" braucht Platz für ein Zeichen, obwohl strlen("") eine 0 liefert.Danke, da habe ich schlicht gepennt, dass strlen den Nullterminator nicht mitzählt.
dinosausdatei->dinoarray = (struct dino **) malloc(sizeof(struct dinodaten*));
Du reservierst hier Speicher für einen Zeiger zu einer dinodaten Struktur, was du aber eigentlich willst, ist Speicher für die Gesamtanzahl an Zeigern für dino Strukturen.
Das war des Rätsels Lösung.
dinosausdatei->dinoarray = (struct dino **) malloc(dinosausdatei->anz*sizeof(struct dinodaten*));
Die anderen Zuweisungen sind jetzt nicht direkt falsch, sondern unnötig, wenn ich es richtig verstanden habe? Das war mir klar, ist allerdings der Aufgabenstellung geschuldet, in der man erst einmal kennenlernen soll, was mit malloc, calloc und free so alles möglich ist. Das Buch soll mich primär auf die Algorithmen- und Datenstrukturenk-Kausur des zweiten Semesters vorbereiten, da kommt so etwas gern dran.
Danke für Eure Hilfe.
-
ColinMacLaren schrieb:
Das war des Rätsels Lösung.
dinosausdatei->dinoarray = (struct dino **) malloc(dinosausdatei->anz*sizeof(struct dinodaten*));
Nein, immer noch falsch.
Du willst Speicher für dino Zeiger reservieren, nicht für dinodaten.
Es läuft dennoch, weil die dino und dinodaten Strukturen die gleiche Speichergröße haben (1 Zeiger + 1 int).ColinMacLaren schrieb:
Die anderen Zuweisungen sind jetzt nicht direkt falsch, sondern unnötig, wenn ich es richtig verstanden habe? Das war mir klar, ist allerdings der Aufgabenstellung geschuldet, in der man erst einmal kennenlernen soll, was mit malloc, calloc und free so alles möglich ist. Das Buch...
Man hätte sicherlich nützlichere Beispiele zum Lernen bereitsstellen können, so gewöhnen sich Anfänger noch falsches Verhalten an