[C] Files berabeiten

rene87

Lieutenant
Registriert
Mai 2007
Beiträge
752
Moin
ich habe wie der Titel schon vermuten lässt ein kleines Problem mit einen Program was files bearbeiten soll.
Die Aufgabe ist folgende:
Es soll in allen Datensatzen mit der Fachrichtungsnummer (Datenfeldname fnr) 10333 diese durch 10300 ersetzt werden.
Das soll mit diesen Programm gemacht werden
Code:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
	char name[20], vorname[20];
	int jahr;
	int fnr;
	char geschlecht;
	int abinote;
} satz_typ;
void Erstellen();
void Auswerten();
void eingabe(satz_typ *s);
void erstellenund();
int main() {
	FILE *f;
	satz_typ s;
	f = fopen("c:\\c\\student.dat", "r+b"); /* binäres File */
	if (f == NULL) {
	perror("Fehler beim Oeffnen"); /* Fehler beim Oeffnen */
	exit(EXIT_FAILURE); /* exit */
	}
	/*** Sukzessives Lesen der Sätze mit fread bis fread 0 zurückgibt ***/
	while (fread(&s, sizeof(satz_typ), 1, f) != 0) {
		if (s.fnr==10333){ /* Verarbeiten des Satzes */
			fseek(f,ftell(f)-sizeof(satz_typ),SEEK_SET);
			s.fnr=10300;
			fwrite(&s, sizeof(satz_typ),1, f);
		}
	}
	fclose(f); /* Schließen der Datei */
	return 0;
}
nur macht es leider nicht das was es soll.
wenn es einen fehlerhaften Datensatz findet ersetzt es ihn nicht durch einen Korrigierten sondern fügt einen Berichtigten hinten an.
Dadurch das es denn Falschen nicht überschreibt bin ich einer Endlosschleife da er immer wieder denn einen falschen Berichtigen will.( aus einer 3KB großen Datei ist nach ca. einer Minute eine 50MB Große geworden)
Der Datensatz und die Datei wurde hier mit erstellt
Code:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
	char name[20], vorname[20];
	int jahr;
	int fnr;
	char geschlecht;
	int abinote;
} satz_typ;
void eingabe(satz_typ *s);
main() {
FILE *f;
satz_typ s;
char ch;
f = fopen("c:\\c\\student.dat", "ab"); /* binäres File */
do {
	eingabe(&s);
	fwrite(&s, sizeof(satz_typ), 1, f); /* sizeof Operator */
	printf("Weiteren Satz? (j/n)"); ch = getchar(); getchar();
	} while ((ch == 'j') || (ch == 'J'));
fclose(f);
return 0;
}
void eingabe(satz_typ *s) {
char ch;
printf("Name: "); gets((*s).name); /* oder s->name */
printf("Vorname: "); gets((*s).vorname);
printf("Geburtsjahr: "); scanf("%d", &(*s).jahr);
printf("Fachrichtungnummer: "); scanf("%d", &(*s).fnr);
printf("Abiturnote: "); scanf("%d", &s->abinote); getchar();
printf("Geschlecht(m/w): "); ch = getchar(); getchar();
if (ch=='m'|| ch=='M') {
	(*s).geschlecht = 'm';
	}else {
	(*s).geschlecht = 'w';
	}
}
selbst unser Übungsleiter konnte innerhalb der Übungszeit keinen Fehler finden
 
Hallo,

ich habs jetzt nicht mit deinem Code ausprobiert, aber versuchs mal so:

Code:
[...]
if (s.fnr==10333){ /* Verarbeiten des Satzes */
			fseek(f,-sizeof(satz_typ),SEEK_CUR);
			s.fnr=10300;
			fwrite(&s, sizeof(satz_typ),1, f);
		}
[...]

Ist ja theoretisch dasselbe, aber der Fehler muss doch in dem Bereich liegen, wenn du schreibst, dass der neue Datensatz hinten angefügt wird... Meinst du damit eigentlich hinter den gerade kontrollierten Datensatz oder ganz "hinten" also am Ende der Datei?

mfg
mitos
 
Zuletzt bearbeitet:
Also, ich weiß nicht, ob das überhaupt so geht; also mittendrin schreiben.

Mach mal vorbeugend vor dem Schreiben ein fflush. Zumindest in C++ wird ein extra Read- und Write-Zeiger in den IOStreams gemanaget. Oder das File nochmal exklusiv zum Schreiben öffnen.

Das Binary-Flag sollte unnötig sein, da du nur unformatierten In/Output machst. Das Binary-Flag sorgt im Wesentlichen dafür, dass \n\r unter Windows in ein einzelnes \n umgewandelt wird, wenn du formatierten Input verwendest, also z.B. fscanf. Daher ist der Name etwas fehlleitend.

Ansonsten fehlt auch noch einiges an Fehlerprüfung. Also feof und ferror. Zusätzlich solltest du die Anzahl Bytes vergleichen, die fread liest, und mit der Größe der Struktur vergleichen. Zumindest eine eher unwahrscheinliche, aber mögliche Fehlerquelle.

Im Wesentlichen würde ich aber nochmal auf den ersten Punkt verweisen. Versuch mal dazu ein wenig zu googlen.
 
Die Datensätze wurden ans ende der Datei herangesetzt.
selbst ein nochmaliges öffnen der Datei nur zum schreiben brachte nichts da denn immer nur der letzte korrigierte Datensatz vorhanden war.
ich habe es jetzt so gemacht das er eine zweite Datei erzeugt die am ende wieder gelöscht wird
 
...vielleicht wäre es sauberer, einfach sämtliche Datensätze einzulesen, die Modifikation durchzuführen und anschließend die komplette Datei neu zu schreiben - wem das zu unsicher ist, könnte zuerst die alte Datei nach ".bak" kopieren; so hätte man noch eine Sicherheitskopie.^^ okay, das ist natürlich stark abhängig von der größe und Anzahl der Datensätze - jedoch sollte man eh ab einer bestimmten Größe lieber auf XML setzen - da klappt das nämlich ganz gut mit, neue Nodes zu stellen oder alte zu bearbeiten - oder gleich eine Datenbank, wie MySQL, zu benutzen.
 
so schaut es jetzt aus
Code:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
	char name[20], vorname[20];
	int jahr;
	int fnr;
	char geschlecht;
	int abinote;
} satz_typ;
int main() {
	FILE *k,*l;
	satz_typ s;
	rename("c:\\c\\student.dat","c:\\c\\studenttemp.dat");
	k = fopen("c:\\c\\student.dat", "wb");
	l = fopen("c:\\c\\studenttemp.dat", "rb");
	if (l == NULL) {
	perror("Fehler beim Oeffnen"); 
	exit(EXIT_FAILURE); 
	}
	while (fread(&s, sizeof(satz_typ), 1, l) != 0) {
		if (s.fnr==10333)s.fnr=10300;
		fwrite(&s, sizeof(satz_typ),1, k);
		}
	fclose(l);
	fclose(k);
	remove("c:\\c\\studenttemp.dat");
	return 0;
}
 
1.) Ich schaue einmal, ob ich den Fehler reproduzieren kann bzw. erklären kann, warum das so läuft. Auf die Schnelle finde ich den Fehler auch nicht

2.) Das b bei Binärdaten ist unbedingt notwendig. Mich hat das da schon einmal aufgeschmissen und ich konnte mir nicht erklären, warum im bei einer 1MB Datei gerade nach 174KB den Fehler hatte. Die Lösung war, dass ich eben die ungünstige Konstellation von CR und LF hatte und er mir das übersetzt hat. Binärdaten müssen zwingend als Binärdaten geöffnet werden, Textdateien als Textdateien.

3.) Bei Read muss man auf jeden Fall immer den Rückgabewert überprüfen. Da kann man nämlich auch ordentlich einfahren. Da füllt das Read nämlich den Buffer von z.B. 10KB nicht mit 10KB sondern mit dem ersten Datenblock, der kommt. Der ist übers LAN meistens um die 70KB, muss aber nicht sein. Ich habe mich da ordentlich gewundert und ewig lange gesucht, weil mein Programm auf localhost super funktioniert, aber übers Internet nicht bzw. selbst lokal habe ich dann festgestellt, dass es mich bei großen Blockgrößen (um die 1MB) mitten im File aufhaut. Das Problem besteht übrigens auch mit Dateien. Wenn man kleinere Blockgrößen hat, dann ist das nicht so kritisch, aber irgendwann stellt es einen auf und das sind die schlimmsten Fehler, die alle 10GB irgendwo zufällig auftreten und keiner weiß warum. Also wenn im ersten Durchlauf nicht alles gelesen wurde, dann muss man so lange lesen, bis man alles hat.

4.) Strukturen sind je nach Compilereinstellung nicht immer gleich groß. Der Compiler füllt meistens auf Vielfache von 4, 8 oder 16 Bytes auf. Du solltest prüfen. Wenn du jetzt angenommen 45 Bytes große Strukturen hast, dann werden die im RAM auf 48 Byte Blöcke aufgefüllt. Sizeof wird hier immer 48 liefern, damit die Pointerarithmetik bei Feldern noch richtig funktioniert. Du würdest hier also auch unnötige Füllbytes mitschreiben. Das an sich ist ja noch nicht das Schlimme. Das Problem ist, dass wenn du z.B. an den Compilereinstellungen etwas herumschraubst, dann kann es gut sein, dass die Files nachher nicht mehr lesbar sind. Das Lesen und Schreiben von ganzen Strukturen ist sowieso eine Unart, die man sich gar nicht angewöhnen sollte. Man sollte nur byte Felder schreiben und lesen und die dann umwandeln. Spätestens wenn du aus dem vor- und nachname einen char* machst, wird es dich da sowieso aufstellen, da du damit nur eine Adresse (z.B. 0x4711) schreibst und dann bein Laden der Datei an der Adresse im RAM natürlich nichts mehr sinnvolles steht. Einmal abgesehen davon wollen vielleicht auch andere Leute, die nicht C/C++ verwenden (und das tut man heute nur mehr zum Lernen bzw. bei sehr CPU bzw. RAM lastigen Anwendungen), dann hat der sowieso keine Möglichkeit deine Strukturen zu verwenden. Einmal abgesehen davon darf die Reihenfolge von Variablen in einer Struktur nicht fix sein.

5.) Benutze nicht die Funktionen fseek bzw. ftell. Diese liefern dir nur 32 Bit Integer zurück. Es gibt aber viele Dateien, die größer werden können.

6.) In diesem Fall würde ich gar kein SEEK_SET (Beginn der Datei) mit ftell nehmen, sondern gleich die Konstante SEEK_CUR. Das ist die aktuelle Position und dann einfach -sizeof(struktur).

7.) So wie ich das sehe, soll hier eine billige Datenbank nachgeahmt werden. Das ist es natürlich nicht möglich jedes Mal eine temporäre Datei anzulegen, wenn die Datenbank 30GB groß ist und man nur 3 Werte ändern muss. Da muss man dann statt 1x Lesen noch zusätzlich 1x Schreiben, 1xLöschen (was so gut wie nicht möglich ist, wenn andere Leute noch darauf zugreifen, aber egal) und einmal verschieben. Mir hätten meine Lehrer damals dafür den Hals umgedreht. Vor allem ist ja gar nicht gewährleistet, dass auch wirklich der Platz für 2 Dateien da ist. Wenn das eine fette 30GB Datenbank auf einem 50GB SCSI RAID ist, dann denkt ja keiner daran, dass das zu wenig sein könnte.

Edit:
8.) Allen Buchstaben (x,y,z,a,b,c usw.) gleich automatisch unterstellen, dass sie weiblich sind ist auch sehr gewagt. Was ist, wenn der eigentlich ein m eintippen wollte und es nur aus Versehen ein n geworden ist (Würstelfinger)

Edit2:
So wie ich das sehe funktioniert das fseek nicht richtig. Das positioniert sich bei mir immer an den Anfang der Datei, egal was ich angebe. Ich habe das Problem runter bis zur Funktion SetFilePointer lokalisieren können, die den Pointer immer auf 0 setzt, egal was man angibt. 64Bit Pointer bringen hier übrigens auch keine Besserung.

Edit3:
Das Beispielprogramm von MS produziert dasselbe Ergebnis. Mit fgets() liefert es aber immer noch die richtiger Ergebnisse. Ich hasse es, wenn die Werte der Watches nicht mit den tatsächlichen übereinstimmern.
Wahrscheinlich gibt es da noch irgendetwas, das man wissen muss, damit es richtig läuft. Vielleicht funktioniert fread bzw. fwrite nicht richtig, ohne dass man noch irgendetwas tut.
 
Zuletzt bearbeitet:
andr_gin schrieb:
Edit:
8.) Allen Buchstaben (x,y,z,a,b,c usw.) gleich automatisch unterstellen, dass sie weiblich sind ist auch sehr gewagt. Was ist, wenn der eigentlich ein m eintippen wollte und es nur aus Versehen ein n geworden ist (Würstelfinger)

Die Eingabe Funktion habe ich aus dem Script von unsern Professor,
wir sollten auch keine Datenbank ersetzten, wir sollten nur denn Umgang mit Files und Strukturen lernen.
Ich hatte das Programm jetzt so abgegeben wie ich es oben gepostet hatte (letzter Post), zwar nicht die beste Variante aber eine drei die Funktioniert.
Es soll irgend was mit den Positionszeiger gewesen sein wieso es nicht geklappt hat beim ersten versuch, was mit fread und fwrite zusammenhängt.
 
andr_gin schrieb:
2.) Das b bei Binärdaten ist unbedingt notwendig.
Nein, nicht zwingend. Read macht schon unformatierten Input. D.h. da wird nix geparst. In dem Fall sollte es ziemlich egal sein, es macht aber auch nix schlimmes.

Bin mir wie gesagt nicht sicher, ob das überhaupt mit fread und fwrite geht. Möglicherweise muss man hier direkt auf Betriebssystem-Routinen gehen.
 
1.) Ich kann nicht sagen, ob es das fread oder das fwrite ist, das den Murks baut, aber eines von beiden ist es definitiv. Ich habe das wie gesagt schon gehabt. Nachdem ich auf b gestellt habe ist es wieder gegangen. Ich würde mir das gar nicht anfangen. Binärdateien sind Binärdateien und Textdateien sind Textdateien, wobei ich im Zweifelsfall hier auch das Binärformat bevorzugen würde und immer ein CR+LF machen beim Schreiben. Nachdem er auch schreibt, ist das ziemlich problematisch. Hier werden nicht nur die Daten verändert, sondern auch die Länge der Daten und da sucht man lange und kommt nicht drauf, warum ausgerechnet die Zahl 3338 (+x mal 65536) falsch geschrieben wird.

Die Eingabe Funktion habe ich aus dem Script von unsern Professor,
wir sollten auch keine Datenbank ersetzten, wir sollten nur denn Umgang mit Files und Strukturen lernen.

Das ist noch umso schlimmer. Damit lernt man den Leuten nicht das saubere Arbeiten. Schau dir z.B. einmal mit Notepad die Daten an, die du da schreibst. Da stehen z.T. irgendwelche Daten drin, die vorher zufällig im RAM waren. Damit ist erstens nicht gewährleistet, dass dieselben Daten auch denselben Output liefern und zweitens ist das besonders Toll, wenn sich das gerade so ausgeht, dass da vorher irgendein Passwort im RAM gestanden ist. Und schon wieder können ein paar 100 Mio. Leute ein Windows Update einspielen und im Schlimmsten Fall ist das wieder ein Virus, der die Hälfte der Rechner infiziert.
 
Zuletzt bearbeitet:
Zurück
Oben