Forum: PC-Programmierung Wann Tastaturpuffer mit fflush(stdin); löschen?


von Tstyle (Gast)


Lesenswert?

Hallo,

hab folgendes Programm in C geschrieben (Auschnitt) :


void main (void)
{
  double a,b, Ergebnis;
  char Rechenzeichen;

  printf("Bitte die erste Variable eingeben\n");
  scanf("%lf", &a);
  printf("Bitte die zweite Variable eingeben\n");
  scanf("%lf", &b);
  printf("Bitte Rechenoperation ausw\x84hlen: ""+,-,*,/""\n");
  fflush(stdin); // Tastaturpuffer leeren, da als nächstes sonst ein 
"Enter" eingelesen würde
  scanf("%c", &Rechenzeichen);

  switch(Rechenzeichen)

....

Das hier abgebildete Programm funktioniert!

Habe beim programmieren zu nächst den "fflush(stdin);" um das "Enter" 
aus dem Tastaturpuffer zu löschen vergessen.

Würde gerne wissen ,warum ich diesen unbedingt an der Stelle brauche, 
bevor ich einen Wert in eine char Variable einlese, aber er nicht 
notwendig
nach dem zweiten scanf-Befehl ist.
Zumindestns zeigt das Programm keine Fehlfunktion an.

Bin gespannt ;-) !

von B. S. (bestucki)


Lesenswert?

> The behavior is undefined if the given stream is of the input type [...]
http://en.cppreference.com/w/c/io/fflush

Hier eine Lösung (Antwort mit der Bewertung 10):
> Quit using scanf. Use fgets and the sscanf
http://stackoverflow.com/questions/6277370/replacement-of-fflushstdin


Leider steht immernoch in vielen Tutorials und sogar in Büchern, dass 
fflush zum Leeren des Tastaturbuffers verwendet werden soll...

: Bearbeitet durch User
von Tstyle (Gast)


Lesenswert?

be stucki schrieb:
> Hier eine Lösung (Antwort mit der Bewertung 10):
>> Quit using scanf. Use fgets and the sscanf
> http://stackoverflow.com/questions/6277370/replacement-of-fflushstdin

Sind "scanf" und "gets" einfach "veraltet"?
Also kann ich 1:1 die hier vorgeschlagenen Befehle benutzen ohne mir 
"Sorgen" machen zu müssen?


Hier ist eine Erklärung warum fgets beser ist:

http://www.proggen.org/doku.php?id=c:lib:stdio:fgets


Unser Lehrer hat bis jetzt nur die Befehle scanf und gets inkl. 
fflush(stdin)
vorgestellt. Soll ich ihn drauf hinweißen das sein Zeug veraltet ist 
oder gibt es einen Grund warum er die "alten" Befehle benutzt?

Danke für die schnelle Antwort!!

von Dirk B. (dirkb2)


Lesenswert?

gets ist veraltet und auch gefährlich, da die Größe des Eingabepuffers 
nicht geprüft wird (und auch nicht kann, da sie gar nicht angegeben 
wird).

Darum ist gets (seit C11) auch deprecated.

Das Verhalten von fflush() ist für stdin nicht definiert.
Unter Linux leert es nicht den Eingabepuffer.

Für dein Problem, das überlesen vom '\n', reicht ein Leerzeichen vor dem 
%c im Formatstring von scanf aus.
1
scanf(" %c", &Rechenzeichen);
2
//     ^ da
Das Leerzeichen bedeutet: "Überlese alle Whitespace"

scanf ist eine sehr mächtige Funktion, die dementsprechend auch 
Resourcen braucht.
Bei fgets brauchst du einen ausreichend großen Speicherbereich für die 
Eingabe. Dies brauchst du bei scanf nicht unbedingt.

fgets speichert allerdings das '\n' mit in der Eingabe ab.
Das gibt gerne mal Probleme beim Stringvergleich oder auch bei 
Dateinamen.

Tstyle schrieb:
> oder gibt es einen Grund warum er die "alten" Befehle benutzt?

Weil er nichts mehr dazu gelernt hat.

von B. S. (bestucki)


Lesenswert?

Tstyle schrieb:
> Sind "scanf" und "gets" einfach "veraltet"?

Veraltet ist das falsche Wort. Die beiden Funktionen sind unsicher und 
können einen Buffer-Overflow erzeugen:
1
#include <stdio.h>
2
3
int main(void){
4
  char Str[16];
5
  scanf("%s",Str);
6
  return 0;
7
}
Wer oder was hindert den Benutzer daran, mehr als 15 Zeichen einzugeben? 
Genau, niemand! scanf hat keine Ahnung über die Grösse von Str und 
schreibt munter alle eingegebenen Zeichen in den Speicher.

Mit fgets kann sowas nicht passieren:
1
#include <stdio.h>
2
3
int main(void){
4
  char Str[16];
5
  fgets(Str,sizeof(Str),stdin);
6
  return 0;
7
}
Der Benutzer kann zwar immernoch mehr als 15 Zeichen eingeben, aber dein 
Speicher wird dabei nicht zerschossen, da fgets eine Längenangabe 
erhalten hat. fgets liest jetzt aber nur die ersten 15 Zeichen ein. Hat 
der Benutzer mehr Zeichen eingegeben, stehen diese immernoch im Input 
Buffer und können später noch gelesen werden.


Tstyle schrieb:
> Soll ich ihn drauf hinweißen das sein Zeug veraltet ist
> oder gibt es einen Grund warum er die "alten" Befehle benutzt?

Kannst ihm ja einfach sagen, dass du im Internet gelesen hättest, dass 
scanf unsicher und somit in professioneller Software nicht zu gebrauchen 
sei und ihn anschliessend fragen, ob dies so richtig sei. Viele Lehrer 
haben Probleme damit, wenn ihre Fähigkeiten direkt in Frage gestellt 
werden. Auf die Tatsache, dass fflush(stdin) undefiniertes Verhalten 
zeigt, solltest du ihn natürlich hinweisen.

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

btw, wie habt ihr gelernt, double-Variablen auszugeben?
Welchen Formatspecifier nutzt ihr dafür?

von Dirk B. (dirkb2)


Lesenswert?

Mit
1
#include <stdio.h>
2
3
int main(void){
4
  char Str[16];
5
  scanf("%15s",Str);
6
  return 0;
7
}
bist wieder auf der sicheren Seite.

Schwieriger wird es, wenn die Länge des Puffers variabel ist.

Aber so einfach kann man fgets und scanf (mit %s) auch nicht 
vergleichen.
Die haben da unterschiedliches Verhalten, was durchaus erwünscht sein 
kann.

von B. S. (bestucki)


Lesenswert?

Dirk B. schrieb:
> fgets speichert allerdings das '\n' mit in der Eingabe ab.
> Das gibt gerne mal Probleme beim Stringvergleich oder auch bei
> Dateinamen.

Ich sehe das als Vorteil:
1
char * NewLineChar = strrchr(Str,'\n');
2
if(NewLineChar == NULL){
3
  /* Zeile nicht vollständig gelesen */
4
}
5
else{
6
  *NewLineChar = '\0';
7
  /* Verarbeitung der eingelesenen Daten */
8
}


Dirk B. schrieb:
> Mit#include <stdio.h>
>
> int main(void){
>   char Str[16];
>   scanf("%15s",Str);
>   return 0;
> }bist wieder auf der sicheren Seite.

Bis du irgendwann die Grösse des Buffers änderst. Da arbeite ich lieber 
mit dem sizeof Operator.


Dirk B. schrieb:
> Aber so einfach kann man fgets und scanf (mit %s) auch nicht
> vergleichen.
> Die haben da unterschiedliches Verhalten, was durchaus erwünscht sein
> kann.

Kannst du ein Beispiel machen? Ich habe scanf bis jetzt noch nie 
vermisst, aber vielleicht kann ich noch was lernen.

von Tstyle (Gast)


Lesenswert?

Dirk B. schrieb:
> #include <stdio.h>
>
> int main(void){
>   char Str[16];
>   scanf("%15s",Str);
>   return 0;
> }

muss nicht am Ende des Arrays ein "\0" eingefügt werden, damit der 
Compiler weiss das die Zeichenkette zu Ende ist.

Die Funktionen gets, fgets machen das ja selbstständig aber scanf?

von Tstyle (Gast)


Lesenswert?

Dirk B. schrieb:
> btw, wie habt ihr gelernt, double-Variablen auszugeben?
> Welchen Formatspecifier nutzt ihr dafür?

printf("%lf", dbl);

von Dirk B. (dirkb2)


Lesenswert?

be stucki schrieb:
> Ich sehe das als Vorteil:

Ich auch. Ich wollte nur darauf hinweisen, dass man gets nicht so 
einfach durch fgets ersetzen kann.

be stucki schrieb:
> Kannst du ein Beispiel machen?

Nein.
Es gibt dann immer ein Beispiel, wie man es ohne scanf macht.

von Tstyle (Gast)


Lesenswert?

be stucki schrieb:
> Kannst du ein Beispiel machen? Ich habe scanf bis jetzt noch nie
> vermisst, aber vielleicht kann ich noch was lernen.

Sry, kann dir darauf leider keine Antwort geben. Bis jetzt haben wir 
diesen Befehl zum Einlesen von Variablen des Typs int, double und char 
benutzt.


Dass man es wie unten gezeigt machen kann, wusste ich bis jetzt noch 
nicht.


Dirk B. schrieb:
> #include <stdio.h>
>
> int main(void){
>   char Str[16];
>   scanf("%15s",Str);
>   return 0;
> }


Dachte immer das man Funktionen wie gets/fgets braucht um eine 
Zeichenkette einlesen zu können.

von Dirk B. (dirkb2)


Lesenswert?

Tstyle schrieb:
> Dirk B. schrieb:
>> btw, wie habt ihr gelernt, double-Variablen auszugeben?
>> Welchen Formatspecifier nutzt ihr dafür?
>
> printf("%lf", dbl);

Auch da sieht man es.

Der richtige Formatspecifier wäre das %f ohne das l (ell)
Das ist eine Besonderheit von Funktionen mit variabler Argumentenliste.

Da aber fast alle das mit %lf gemacht haben, wurde es geduldet und ist 
mittlerweile auch im Standard enthalten.
Siehe http://www.cplusplus.com/reference/cstdio/printf/
oder http://en.cppreference.com/w/c/io/fprintf

von Dirk B. (dirkb2)


Lesenswert?

Tstyle schrieb:
> Dachte immer das man Funktionen wie gets/fgets braucht um eine
> Zeichenkette einlesen zu können.

fgets liest eine ganze Zeile ein (oder bis der Puffer voll ist)
scanf mit %s liest bis zum nächste Whitespace (oder bis die angegebene 
Länge erreicht wurde)

Es gibt dann noch %[
Damit kann man auch hübsche Dinge anstellen. Z.B gets simulieren.
1
int main(void){
2
  char Str[16];
3
  scanf("%15[^\n] ",Str);
4
  return 0;
5
}

Bei der Größenangabe bei scanf muss man aber Bedenken, dass dort die 
Anzahl der Zeichen angegeben wird. Der Platz für die '\0' muss noch 
übrig bleiben.
Bei fgets wird die Größe des Puffers angegeben. Die '\0' wird 
berücksichtigt.

von Tstyle (Gast)


Lesenswert?

Dirk B. schrieb:
> Der richtige Formatspecifier wäre das %f ohne das l (ell)

In unserem Skriptum steht der Formatspecifier f für den Datentyp float.

Laut Definiton von

Dirk B. schrieb:
> oder http://en.cppreference.com/w/c/io/fprintf

steht der f also für Gleitkommazahlen jeglichen Datentyps?

von Dirk B. (dirkb2)


Lesenswert?

Tstyle schrieb:
> Laut Definiton von
>
> Dirk B. schrieb:
>> oder http://en.cppreference.com/w/c/io/fprintf
>
> steht der f also für Gleitkommazahlen jeglichen Datentyps?

Nein.

Das steht da auch nicht.
Es gibt ja noch long double.
Dafür ist der L-Modifier. Also %Lf

In der Reference steht beim l-Modifiere double.
Allerdings steht das auch bei none

von Rolf Magnus (Gast)


Lesenswert?

be stucki schrieb:
> Leider steht immernoch in vielen Tutorials und sogar in Büchern, dass
> fflush zum Leeren des Tastaturbuffers verwendet werden soll...

Ja, das sieht man recht oft. Dabei ist das ja überhaupt nicht der Sinn 
von fflush(). Der Sinn ist, alle Daten, die noch im Puffer liegen, an 
ihr Ziel auszugeben. Bei Eingabestreams ergibt das keinen Sinn, denn da 
ist das Ziel ja mein Programm, wohin ich die Daten duch einfaches Lesen 
bekomme.
Es gibt keine Funktion, um den Pufferinhalt eines Streams zu verwerfen.

von Tenga (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Es gibt keine Funktion, um den Pufferinhalt eines Streams zu verwerfen.

Nicht blockierend lesen bis nichts mehr kommt.

von Rolf Magnus (Gast)


Lesenswert?

Tenga schrieb:
> Rolf Magnus schrieb:
>> Es gibt keine Funktion, um den Pufferinhalt eines Streams zu verwerfen.
>
> Nicht blockierend lesen bis nichts mehr kommt.

Natürlich kann man lesen und dann selbst das Gelesene verwerfen. Das 
habe ich nicht bestritten.

von tictactoe (Gast)


Lesenswert?

Tstyle schrieb:
> Dirk B. schrieb:
>> Der richtige Formatspecifier wäre das %f ohne das l (ell)
>
> In unserem Skriptum steht der Formatspecifier f für den Datentyp float.

Bei den printf-Varianten ist die Unterscheidung zwischen %f und %lf 
nicht notwendig, weil ein float-Wert als Argument einer variablen 
Parameterliste immer nach double konvertiert wird. Bei scanf ist sehr 
wohl wichtig, dass man zwischen %f und %lf unterscheidet:
1
float x;
2
double y;
3
scanf("%f%lf", &x, &y);
4
printf("x: %f, y: %f\n", x, y);

von Karl H. (kbuchegg)


Lesenswert?

Dirk B. schrieb:

> Da aber fast alle das mit %lf gemacht haben, wurde es geduldet und ist
> mittlerweile auch im Standard enthalten.

Da gabs meines Wissens aber auch einen handfesteren Grund.
Die Sache ist die, das eine gleichermassen erlaubte Verwendung von %f 
unf %lf obwohl eigentlich überflüssig, es einfacher macht, Formatstrings 
extern zu warten. Damit kann man in einer externen Datei den zu 
benutzenden Formatspezifier definieren (um ihn zb vom Programm von dort 
lesen zu lassen), ohne sich um die Unterscheidung zwischen printf und 
scanf kümmern zu müssen. Selbiges mit Formatspezifiern, die über #define 
im Code an einer zentralen Stelle gesammelt werden (um zb zwischen %f 
und %g 'umschalten' zu können).

Alles in allem denke ich, das es eine gute Entscheidung war, hier gleich 
zu ziehen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.