Benötige Hilfe bei einer Funktion zum Einlesen einer Datei
-
Hallo liebe Community,
ich hätte da ein kleines Problemchen und würde mich über eure Hilfe
sehr freuen.Ich versuche gerade eine Funktion zu schreiben, die eine Textdatei zum zeilenweisen Lesen öffnet und den Inhalt der Datei dann mittels strcat zeilenweise in einen zuvor reservertien Speicherbereich anhängt.
Ziel ist es dann, sich den Inhalt aus dem Speicherbereich über die Standardausgabe ausgeben zu lassen.Doch leider bin ich bisher nicht wirklich erfolgreich mit diesem Versuch. Dummerweise komme ich auch nicht darauf, woran es letztendlich hakt.
Daher würde ich mich sehr über eure Hilfe freuen. Hier erstmal der Quellcode:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define ZEILE 255 #define BLOCK 10 enum { ERFOLG, FEHLER }; /* ***vektor == zeigt auf einen Zeiger einer Zeile mit einem Zeiger auf die Länge der Zeile * dateiname == Name der zu öffnenden Datei * *zeile_n == Zeiger auf Anzahl der Zeilen * Rückgabewert: ERFOLG wenn kein Fehler, FEHLER wenn Fehler */ int datei_lesen(char ***vektor, char *dateiname, int *zeile_n) { char puffer[ZEILE] = { 0 }; /* Puffer der zeilenweise einliest */ char ***vektor_zeiger; /* zum vergrößern des Speichers für vektor */ char *zeiger; /* zum vergrößern des Speichers für die Anzahl der Zeichen in einer Zeile */ char *neuezeile = NULL; /* Zeiger für neue Zeile */ FILE *datei; /* Datei zum Öffnen */ int fehler = ERFOLG; /* Fehlerstatus */ int zeile = 0; /* altuelle Zeile */ int alt_zeile_n; /* Anzahl der Zeilen für die neuer Speicherplatz reserviert werden soll */ int absatz_n; *zeile_n = 0; if(BLOCK > 0 && ZEILE > 0) { /* Speicher für 10 Zeilen reservieren */ vektor = malloc(BLOCK * sizeof(***vektor)); if(NULL != vektor) { /* für jede Zeile jeweils Platz für 255 Bytes reservieren */ for(zeile = 0; zeile < BLOCK; zeile++) { vektor[zeile] = malloc(ZEILE * sizeof(*vektor[zeile])); if(NULL == vektor[zeile]) fehler = FEHLER; else vektor[zeile][0] = '\0'; } } if(FEHLER == fehler) /* Bei Fehler, Speicher wieder freigeben */ { if(vektor != NULL) { for(zeile = 0; zeile < BLOCK; zeile++) { if(vektor[zeile] != NULL) free(vektor[zeile]); } } free(vektor); vektor = NULL; } } if(NULL != vektor) { datei = fopen(dateiname, "r"); if(NULL != datei) { *zeile_n = BLOCK; absatz_n = 0; /* solange kein Fehler, zeilenweise einlesen */ while(ERFOLG == fehler && NULL != fgets(puffer, ZEILE, datei)) { neuezeile = strchr(puffer, '\n'); /* Newline gegen Terminierungszeichen austauschen */ if(NULL != neuezeile) *neuezeile = '\0'; strcat((*vektor)[zeile], puffer); if(NULL != neuezeile) { absatz_n = 1; zeile++; /* Falls kein Platz mehr für Zeilen im Speicher, zusätzlich reservieren */ if(zeile >= *zeile_n) { alt_zeile_n = *zeile_n; /* Bei negativem Wert, Zeilen entfernen */ if(BLOCK < 0) { for(zeile = alt_zeile_n-1; zeile >= alt_zeile_n + BLOCK; zeile--) free((vektor)[zeile]); } /* Speicher für einzelne Zeilen reservieren */ vektor_zeiger = realloc(vektor, (alt_zeile_n + BLOCK)*sizeof(***vektor)); if(NULL != vektor_zeiger) { vektor = vektor_zeiger; for(zeile = alt_zeile_n; zeile < alt_zeile_n + BLOCK; zeile++) { /* Speicher für Anzahl der Zeichen, die jede Zeile aufnehmen kann */ (*vektor)[zeile] = malloc(ZEILE); if(NULL != (vektor)[zeile]) (vektor)[zeile][0] = '\0'; else fehler = FEHLER; } *zeile_n += BLOCK; } else fehler = FEHLER; } } else { /* Falls kein Newline, dann ist die Zeile länger als 255 Bytes */ absatz_n = 2; /* Mehr Speicher für Anzahl der Zeichen in der Zeile reservieren */ zeiger = realloc(vektor[zeile], absatz_n * ZEILE); if(zeiger != NULL) vektor[zeile] = zeiger; else fehler = FEHLER; } } fclose(datei); } else fehler = FEHLER; /* Falls Datei nicht geöffnet werden kann bzw. nicht existiert */ } else fehler = FEHLER; /* Falls Speicherallokation fehlschlägt */ return(fehler); }
Bitte helft mir. Was ist am Quellcode bei der Speicherreservierung falsch?
-
Dein ***vektor hat drei (3) *. Du behandelst es aber, als hätte es nur 2.
-
Lancy78 schrieb:
int datei_lesen(char ***vektor, char *dateiname, int *zeile_n) { ... vektor = malloc(BLOCK * sizeof(***vektor)); ... vektor[zeile] = malloc(ZEILE * sizeof(*vektor[zeile]));
Mind. die zitierten Zeilen stimmen nicht.
Die Frage wäre auch, was du überhaupt an die Funktion übergibst.
Wenn du sowieso mit konstanten Größen arbeitest, warum übergibst du dann nicht gleich z.B. ein Stringarray?char zeilen[BLOCK][ZEILE]; ... datei_lesen(zeilen,);
-
So etwas ähnliches hatten wir gerade für eine Textdatei.
char zeile[255]; ifstream input; int size; size = sizeof(zeile); input.open("file.txt",ios::in); while(!input.eof()) { input.getline(zeile,size); // hier zeile zur Weiterverarbeitung auswerten } input.close();
Dieser Weg über C++ mit ifstream erscheint mir am einfachsten.
Vielleicht reicht das?
-
Wutz schrieb:
Lancy78 schrieb:
int datei_lesen(char ***vektor, char *dateiname, int *zeile_n) { ... vektor = malloc(BLOCK * sizeof(***vektor)); ... vektor[zeile] = malloc(ZEILE * sizeof(*vektor[zeile]));
Könntest du mir anhand deinem zitierten Code (oben) zeigen, wie es richtig aussehen müsste, bei ***vektor?
Mind. die zitierten Zeilen stimmen nicht.
Die Frage wäre auch, was du überhaupt an die Funktion übergibst.
...[/cpp]Also in der main()-Funktion sieht es so aus:
int main() { char **array = NULL; // wird an die Funktion übergeben char datei[255]; // wird an die Funktion übergeben int zeilen = 0; // wird an die Funktion übergeben ... /* Funktion soll dann folgendermaßen aufgerufen werden */ if((datei_lesen(&vektor, datei, &zeilen) == FEHLER)) printf("Fehler beim Lesen in Speicher!\n"), break; ...
DirkB schrieb:
Dein ***vektor hat drei (3) *. Du behandelst es aber, als hätte es nur 2.
Das ist eben das, wo ich jetzt noch so meine Probleme habe zu verstehen wie man es richtig macht bei ***vektor. Ich bekomme es einfach nicht hin. Deswegen würde ich mich sehr freuen, wenn ihr mir dabei helfen könntet.
Als Fehlermeldung, bei dem Versuch die datei_lesen-Funktion aufzurufen, ist Segmentation fault.
Ich habe im Quellcode testweise ein paar printf()'s eingebaut, um zu beobachten was so alles passiert beim Funktionsaufruf.
Dann fiel mir auf, das nach dem sechsten Durchlauf der while-Schleifewhile(ERFOLG == fehler && NULL != fgets(puffer, ZEILE, datei))
Segmentation fault ausgegeben wird. Doch ich im weiteren Verlauf des Programms wird es noch zu einigen Fehlermeldungen kommen.
Meine Frage an euch:
Bei char ***vektor - wie muss ich das korrigieren (siehe unten)
hier:
vektor = malloc(BLOCK * sizeof(***vektor)); if(NULL != vektor) { for(zeile = 0; zeile < BLOCK; zeile++) { vektor[zeile] = malloc(ZEILE * sizeof(*vektor[zeile])); if(NULL == (vektor)[zeile]) fehler = FEHLER; else (vektor)[zeile][0] = '\0'; } } /* und hier: */ vektor_zeiger = realloc(vektor, (alt_zeile_n + BLOCK)*sizeof(***vektor)); if(NULL != vektor_zeiger) { vektor = vektor_zeiger; for(zeile = alt_zeile_n; zeile < alt_zeile_n + BLOCK; zeile++) { (vektor)[zeile] = malloc(ZEILE); if(NULL != (vektor)[zeile]) (vektor)[zeile][0] = '\0'; else fehler = FEHLER; } *zeile_n += BLOCK; }
Ich hoffe, ich sprenge jetzt nicht den Rahmen eurer Hilfe.
Gruß
Lancy78
-
Was hast du korrigiert?
Du hast immer noch nicht den Unterschied zw. *** und ** verstanden.
Die sizeof-Versuche sind sinnlos, falsch und machen nicht das, was du erreichen willst.
-
Wutz schrieb:
Was hast du korrigiert?
Du hast immer noch nicht den Unterschied zw. *** und ** verstanden.
Die sizeof-Versuche sind sinnlos, falsch und machen nicht das, was du erreichen willst.Ich habe noch nichts korrigiert, sondern gefragt ob ihr mir dabei helfen könntet es zu korrigieren. Desweiteren habe ich in meinem zweiten Post nur hinzugefügt, wie ich die Funktion datei_lesen() in der main()-Funktion aufrufe und was ich der Funktion übergebe.
-
Habe ich zwar schon im Dutzend hier gezeigt, aber hier nochmal, wie man ein Stringarray aus einer Textdatei befüllt.
int datei_lesen(char ***vektor, char *dateiname, int *zeile_n) { char puffer[ZEILE] = { 0 }; /* Puffer der zeilenweise einliest */ FILE *datei; /* Datei zum Öffnen */ *zeile_n = 0; *vektor = 0; datei = fopen(dateiname, "r"); if(NULL != datei) { char *neuezeile = puffer; /* solange kein Fehler, zeilenweise einlesen */ while(fgets(puffer, ZEILE, datei)) { if(NULL != neuezeile) { *vektor=realloc(*vektor,++*zeile_n*sizeof**vektor); (*vektor)[*zeile_n-1]=calloc(1,ZEILE); } neuezeile = strchr(puffer, '\n'); /* Newline gegen Terminierungszeichen austauschen */ if(NULL != neuezeile) *neuezeile = '\0'; (*vektor)[*zeile_n-1]=realloc((*vektor)[*zeile_n-1],1+strlen((*vektor)[*zeile_n-1])+strlen(puffer)); strcat((*vektor)[*zeile_n-1], puffer); } fclose(datei); } return 1; }
Sonderbehandlung für letzte Zeile mit/ohne '\n' und zeile_n fehlt noch, kannst du aber selbst machen.
-
Okay - cool. Vielen vielen Dank für deine Hilfe. Werde ich morgen testen und danach posten ob es klappt.
-
So - habe es nun endlich geschafft. Natürlich ist es bestimmt noch deutlich verbesserungswürdig, aber zumindest funktioniert es erst einmal.
Hier der Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define ZEILE 255 #define BLOCK 10 enum { ERFOLG, FEHLER }; int datei_lesen(char ***vektor, char *dateiname, int *zeile_n) { char puffer[ZEILE] = { 0 }; char ***vektor2; char **vektor_zeiger; char *neuezeile = NULL; FILE *datei; int fehler = ERFOLG; int zeile = 0; int alt_zeile_n; *zeile_n = 0; if(BLOCK > 0 && ZEILE > 0) { vektor_zeiger = malloc(BLOCK * sizeof(*vektor_zeiger)); if(NULL != vektor_zeiger) { for(zeile = 0; zeile < BLOCK; zeile++) { vektor_zeiger[zeile] = malloc(ZEILE * sizeof(*vektor_zeiger[zeile])); if(NULL == vektor_zeiger[zeile]) fehler = FEHLER; else vektor_zeiger[zeile][0] = '\0'; } } if(FEHLER == fehler) { if(vektor_zeiger != NULL) { for(zeile = 0; zeile < BLOCK; zeile++) { if(vektor_zeiger[zeile] != NULL) free(vektor_zeiger[zeile]); } } free(vektor_zeiger); vektor_zeiger = NULL; } } *vektor = vektor_zeiger; if(NULL != *vektor) { datei = fopen(dateiname, "r"); if(NULL != datei) { *zeile_n = BLOCK; zeile = 0; while(ERFOLG == fehler && NULL != fgets(puffer, ZEILE, datei)) { neuezeile = strchr(puffer, '\n'); if(NULL != neuezeile) *neuezeile = '\0'; strcat((*vektor)[zeile], puffer); if(NULL != neuezeile) { zeile++; if(zeile >= *zeile_n) { vektor2 = vektor; alt_zeile_n = *zeile_n; vektor_zeiger = realloc(*vektor2, (alt_zeile_n + BLOCK) * sizeof(**vektor2)); if(NULL != vektor_zeiger) { *vektor2 = vektor_zeiger; for(zeile = alt_zeile_n; zeile <= (alt_zeile_n) + BLOCK; zeile++) { (*vektor2)[zeile] = malloc(ZEILE); if(NULL != (*vektor2)[zeile]) (*vektor2)[zeile][0] = '\0'; else fehler = FEHLER; } vektor = vektor2; zeile -= 10; *zeile_n += BLOCK; } else fehler = FEHLER; } } } fclose(datei); } else fehler = FEHLER; } else fehler = FEHLER; return(fehler); }
Nochmals vielen Dank an alle, aber vor allem an Wutz. Dank deinem Beispiel bin ich letztendlich auf eine Lösung gekommen, die auch funktioniert.
Liebe Grüße an ALLE
Lancy78
-
Ich bin in Fällen wie diesem, in denen es leicht vermeidbar ist, immer dagegen, zwanzig Millionen Speicherblöcke einzeln anzufordern - wenn man tatsächlich mal in Probleme läuft, ist die Fehlerbehandlung auf diese Weise grauenvoll.
Ich habe hier vor längerer Zeit schon mal einen Ansatz vorgeschlagen, der dem Anfänger zunächst vielleicht etwas wild erscheinen mag, aber diese Problematik umschifft: Type-Punning. Ich habe eine einfache Beispielimplementation noch hier herumliegen:
#include <assert.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> struct string_array { size_t size; char **data; }; struct string_array *get_file_contents(char const *fname) { char buf[16 * 1024]; char *p; FILE *fd; size_t i; size_t line_count = 1; size_t file_size = 0; size_t bytes_read; struct string_array *result; fd = fopen(fname, "r"); if(fd == NULL) return NULL; /* * Zwei-Pass-Verfahren: Erst die Zeilen zählen, um dann den großen Plan (s.u.) * umsetzen zu können. */ do { p = buf; bytes_read = fread(buf, 1, sizeof(buf), fd); file_size += bytes_read; while((p = memchr(p, '\n', buf + bytes_read - p))) { ++p; ++line_count; } } while(bytes_read != 0); rewind(fd); /* * Der Plan ist, den Array-Kopf, den Index und die Daten direkt hintereinander in * einen großen Speicherblock zu legen, d.h. * * | size | data | i1 | i2 | i3 | ... | "foo" | "bar" | "baz" | ... | * +------+------+----+----+----+-----+-------+-------+-------+-----+ * |___^ | | | ^ ^ ^ * |____|____|_________| | | * |____|_________________| | * |_________________________| */ result = malloc(sizeof(struct string_array) + /* Kopf */ sizeof(char*) * line_count + /* Index */ file_size + 1); /* Inhalt */ if(result == NULL) return NULL; result->size = line_count; result->data = (char**) (result + 1); /* Der Index liegt direkt hinter dem Struct */ result->data[0] = (char* ) (result->data + line_count); /* und die Daten direkt hinter dem Index */ p = result->data[0]; /* Gesamte Datei auf einmal einlesen */ bytes_read = fread(p, 1, file_size, fd); assert(file_size == bytes_read); fclose(fd); p[file_size] = '\0'; /* Dann Zeilenumbrüche durch '\0' ersetzen (Also die Zeilen voneinander trennen) * und den Index aufbauen: */ for(i = 1; i < line_count; ++i) { p = memchr(p, '\n', result->data[0] + file_size - p); /* Wir wissen, dass das nie NULL wird */ *p++ = '\0'; result->data[i] = p; } /* Eine leere letzte Zeile wird per Konvention nicht als Zeile aufgefasst. */ if(*result->data[result->size - 1] == '\0') --result->size; return result; } int main(int argc, char *argv[]) { struct string_array *file_contents; size_t i; file_contents = get_file_contents(argc > 1 ? argv[1] : __FILE__); if(file_contents == NULL) return -1; for(i = 0; i < file_contents->size; ++i) { puts(file_contents->data[i]); } /* * Freigabe auf einen Schlag hier. */ free(file_contents); return 0; }
Wie man sieht, entfällt die malloc-bezogene Fehlerbehandlung nahezu vollständig, weil nur einmal Speicher angefordert wird. Der Nachteil hier ist, dass die Datei zweimal durchlaufen wird; in der Zeit der Kassettenlaufwerke hätte sich so etwas verboten, heute ist es nicht pauschal zu sagen, ob das ein Problem sein kann. Ggf. ist die Problematik nicht allzu schwer zu umgehen (fstat bzw. _fstat sind dein Freund), ich habe mich bislang nur nie hingesetzt und es gemacht.
Technisches P.S.: Probleme mit dem Alignment treten nicht auf, weil alle Zeiger die gleiche Größe und die gleiche Alignmentanforderung haben. data in struct string_array muss für einen Zeiger richtig ausgerichtet sein, also muss es auch der Speicher direkt dahinter sein. Darauf folgen nur chars, und die haben ein Alignment von 1 (Alignment kann nie größer als der Typ selbst sein, sonst wären Arrays unmöglich).