double Zahl ohne scanf() einlesen
-
Hallo,
ich habe Probleme mit scanf() (Pufferüberläufe) und damit im prinzip auch Sicherheitslücken im Code. Im Netz wird imemr wieder gepredigt, dass man scanf() nicht verwenden soll.
Ich habe die Onlinedokumentation C von A bis Z dazu gelesen und ebenso einen englischen Bericht:
http://poldi.endofinternet.org:55556/BS/c_von_a_bis_z/c_018_022.htm#RxxobKap01802204002A2C1F01018C
http://www.gidnetwork.com/b-60.htmlWie würdet ihr eine double Zahl einlesen, 20 Stellen Genauigkeit? Wenn etwas anderes als Ziffern oder der Punkt eingegeben wird, muss es einen Fehler o.ä. geben.
Grüße
Tim
-
Mit getchar die Zeichen einzeln einlesen und dann im selbstgebastelten Code überprüfen, ob es sich um eine korrekte Eingabe handelt.
-
Etwas in der Art:
double get_number(char const *prompt) { char buf[100] = ""; double x; do { printf("%s", prompt); fflush(stdout); fgets(buf, 100, stdin); } while(sscanf(buf, "%lf", &x) != 1); return x; } /* ... */ double y = get_number("Bitte Zahl eingeben: ");
-
Gewöhn dir den "char buffer[wirdschonreichen]"-Stil garnicht erst an. Erstens ist es falsch, zweitens Verschwendung. Bei Programmen mit 10 oder 100 Zeilen mag das noch gehen, aber ab 1000 Zeilen Code ist sowas nicht mehr verstehbar.
Und wenn du sowas wie "char buffer[100]" schreibst musst du kommentieren wieso da 100 steht. Wieso 100 und nicht 99 und nicht 101. Jetzt weißt du es vielleicht auch so, in 2 Wochen nicht mehr.
Mal abgesehen davon sollten Konstanten als solche deklariert werden.
//code const int bufferlength = 100; //Eingabepuffer für die Eingabe der floatzahl //100 weil mir gerade so ist aber eigentlich ist es ein bug //code fgets(buf, bufferlength, stdin); //TODO: Testen ob bufferlength ausgereicht hat. Wenn nicht muss noch irgendwas passieren //code
Dann kann man auch mal eben den Buffer größer machen ohne das ganze Programm zu durchsuchen und verstehen zu müssen. Wie gesagt, bei 10 Zeilen ist das zumutbar, bei was größerem nicht mehr.
-
Hallo nwp2,
ich habe versucht dein Minimalbeispiel zum Laufen zu bringen.
#include <stdio.h> int main(void) { double buf; const int bufferlength = 100; //Eingabepuffer für die Eingabe der floatzahl fgets(buf, bufferlength, stdin); printf("%lf", buf); return 0; }
So funktioniert es leider nicht:
gcc -lm -Wall -Wextra -std=gnu99 -pedantic -o "3" "3.c" (im Verzeichnis: C:\Users\xyz\test)
3.c: In functionmain': 3.c:8: warning: passing arg 1 of
fgets' from incompatible pointer type
3.c:9: warning: double format, pointer arg (arg 2)
Kompilierung erfolgreich beendet.Außerdem verstehe ich den Vorteil nicht. Du gibts doch ebenfalls die Größe des Eingabepuffers an?!
Gruß
Tim
-
Katzenstreu schrieb:
ich habe Probleme mit scanf() (Pufferüberläufe) und damit im prinzip auch Sicherheitslücken im Code. Im Netz wird imemr wieder gepredigt, dass man scanf() nicht verwenden soll.
Predigten sollte man schon in der Kirche keinen Glauben schenken, warum also im Netz? Wo soll denn bei
scanf("%lf", &number);
ein Pufferüberlauf sein?
-
Wo hast du denn Probleme mit scanf? Richtig angewendet gibt es keine Probleme. Pufferüberläufe gibts i.d.R. beim unachtsamen Einlesen von Zeichenketten.
Den Rückgabewert von scanf kannst du für die Fehlerprüfung benutzen. Ein nachfolgendes getchar() kann man aufrufen um zu prüfen, ob hinter der Zahl noch weitere Zeichen folgen und diese je nach Bedarf als einen Fehler werten.Gruß,
B.B.
-
Die Länge des Buffers ist in diesem Fall höchst selten von Bedeutung, weil keine Buffer-Overflows erzeugt werden können. Sicher, jemand, der dir absichtlich Blödsinn eingibt, kann auf die Art merkwürdiges Verhalten erzeugen, aber wenn es mit böser Absicht davorsitzt, kann er den Blödsinn auch einfach einzeln eingeben.
Die grundsätzliche Überlegung ist, dass Benutzereingaben auf der Konsole am sinnvollsten Zeilenweise abzuarbeiten sind, deswegen erst die Eingabe mit fgets und das eigentliche Parsen mit sscanf - auf die Art hast du keine Rückstände im Puffer, und falsch geformte Eingaben werden ignoriert.
Natürlich kann man das ganze auch mit dynamischer Pufferbreite betreiben (GNU bietet dafür beispielsweise eine Funktion getline), aber für ein einfaches Beispiel war es mir schlicht zu viel Arbeit, das zusammenzuschreiben. Aber bitte:
#include <stdio.h> #include <stddef.h> #include <stdlib.h> char *get_line(FILE *stream) { static size_t const CHUNKSIZE = 256; size_t turn; char *p = NULL; for(turn = 0; ; ++turn) { size_t pos = turn * CHUNKSIZE - !!turn; size_t siz = (turn + 1) * CHUNKSIZE; char *q = realloc(p, siz); if(!q) { free(p); return NULL; } p = q; p[siz - 2] = '\0'; if(!fgets(p + pos, siz - pos, stream)) { free(p); return NULL; } if(p[siz - 2] == '\0' || p[siz - 2] == '\n') { break; } } return p; } double get_number(char const *prompt) { char *line = NULL; double x; do { free(line); printf("%s", prompt); fflush(stdout); line = get_line(stdin); if(!line) { /* TODO: Fehlerbehandlung */ } } while(sscanf(line, "%lf", &x) != 1); free(line); return x; }
-
seldon schrieb:
Die Länge des Buffers ist in diesem Fall höchst selten von Bedeutung, weil keine Buffer-Overflows erzeugt werden können.
Nein?
char double_number_fake; scanf ( "%lf", &double_number_fake );
:p
-
Lieder kann ich euch nicht immer folgen.
Aber warum ist es so schwer Gleitkommazahlen (z.B. double) von der Tastatur einzulesen?
Bei der Eingabe anderer Zeichen außer der 0 bis 9 und dem Punkt soll so lange weiter auf die Eingabe gewartet werden, bis diese gültig ist.Ich weiß nicht woran ich mich halten soll. Es gibt viele Meinungen, die sich nicht selten widersprechen, die es mir als Anfäner schwer machen, der doch nur ein paar Zahlen in sein Programm bekommen möchte :).
Wodurch der Überlauf o.ä. entstanden ist, weiß ich leider nicht. Ein Freund stellte das gleiche Verhalten an meinem Programm fest. Wahrscheinlich hatte es mit dem Newlinezeichen (\n) zu tun. Das ziehe ich nun mit
do {scanf("%c",&Tabellenart);} while (getchar() != '\n')
raus. Den ganzen Code könnte ich hier zwar veröffentlichen, ber der ist 150 Zeilen lang.
-
Katzenstreu schrieb:
Lieder kann ich euch nicht immer folgen.
Aber warum ist es so schwer Gleitkommazahlen (z.B. double) von der Tastatur einzulesen? Bei der Eingabe anderer Zeichen außer der 0 bis 9 und dem Punkt soll so lange weiter auf die Eingabe gewartet werden, bis diese gültig ist.Sooo schwer ist es ja gar nicht. Du beschreibst hier jedoch eine neue Anforderung an den Code. Aus dieser Anforderung ergibt sich, das scanf zum Einlesen eines Zeichens nicht so gut geeignet ist.
do {scanf("%c",&Tabellenart);} while (getchar() != '\n')
Tendenziell ist obiger Ansatz ok, wenn auch recht mühsam.
Die Buchstaben müssen einzeln geprüft und in einen Puffer geschrieben werden.
Danach muss eine Umwandlung nach double erfolgen. Bedenke das der Benutzer auch zwei mal nen '.' eingeben kann, Vorzeichen sollten möglich sein und eine Eingabe im Exponentialformat sollte möglich sein, etc. Das ist viel Aufwand!
Statt scanf würde ich in diesem Fall getchar nehmen.
So würde ich aber nur dann vorgehen, wenn es nicht anders ginge.Ansonsten würde ich die Arbeit scanf, bzw. fgets und strtod überlassen:
int read_double_from_scanf ( double* number ) { if ( 1 != scanf ("%lf", number) ) { puts ("ERROR: No double number read!"); return 1; } else { puts ("OK: Double number read!"); if ( getchar() != '\n' ) { puts ("Extra character(s) were typed at the end of the input!"); return 1; } } return 0; } void rem_lf ( char* buf ) { char* p = strrchr( buf, '\n' ); if ( p ) *p = 0; } int read_double_from_gets ( double* number ) { char buf[256] = {0}; char* eptr = 0; fgets ( buf, sizeof(buf), stdin ); rem_lf ( buf ); if ( *buf == 0 ) { puts ("ERROR: Empty input!"); return 1; } strtod ( buf, &eptr ); if ( eptr == buf ) { puts ("ERROR: No number read!"); return 1; } if ( eptr != 0 ) { puts ("Extra character(s) were typed at the end of the input!"); return 1; } return 0; }
Gruß,
B.B.