Sauber und sichere Eingabe & Wie entleere ich den Inputbuffer



  • Hallo,
    ich habe folgendes Problem. Wenn mein Anwender bei einem cin zwei Werte auf einmal mit einem Leerzeichen getrennt eingibt, liesst C++ wie vorgesehen bis zum Leerzeichen ein und belaesst den Rest nach dem Leerzeichen im Inputbuffer.

    Beim naechsten cin verwendet C++ den bereits vorhanden Rest im Inputbuffer und laesst meinen Anwender nichts eingeben, sonder befuellt die uebergebene Variable gleich selbst aus dem Inputpuffer. Ich weiss aus dem Buch "C++ von A bis Z", dass das ordentliches C++ Verhalten ist.

    // Beispiel fuer c-plusplus.net
    #include <iostream>
    using namespace std;
    
    int main()
    {
    	int a = 0;
    	int b = 0;
    	cout << "Geben sie Variable a ein:";
    	cin >> a; // Eingabe durch Anwender: 12 34
    	cout << a << '\n'; // Ausgabe 12
    	cout << "Geben Sie Variable b ein:";
    	cin >> b; // Keine Eingabe durch Anwender, da Programm sofort weiterlaeuft.
    	cout << b << '\n'; // Ausgabe 34
    	return 0;
    }
    
    • Wie sichert man gute Shellprogramme allgemein gegen Eingabefehler ab?
    • Wie kann ich den Inputpuffer komplett entleeren, falls trotz gesicherter Eingabe solche Reste enthaelt?

    Inzwischen weiss ich, durch ueberblaettern einiger Kapitel, dass ich den Status des Inputstreams pruefen kann. Aber wegen Eingaben mit einem Leerzeichen sollte der Inputbuffer ja nicht auf fail oder bad stehen?

    Vielen Dank im Voraus

    Exkurs fuer neugierige Leser:
    In einem Lernprogramm will ich meinen Neffen durch abspielen kleiner OGG-Dateien belohnen. Wer erraet, aus welchem Spiel die Musik stammt bekommt einen extra Keks 😉

    system("ogg123 smb3_powerupp.ogg"); // schiebt den Returncode von ogg123 in den Inputstream, und verhindert saubere Eingaben mit cin im weiteren Programmverlauf?
    

    Diesen Systemaufruf habe ich mit Google gefunden, wahrscheinlich ist das keine saubere Art ein Musikdatei abzuspielen (sicher gibt es Libraryfunktionen fuer sowas?), aber so bin ich auf das Problem des verschmutzten Inputpuffers gestossen. Obwohl die Eingaben korrekt gewesen sein moegen.



  • Das dürfte wohl Super Mario Bros 3 sein.

    Wie dem auch sei, üblicherweise reicht es, den Rest der Zeile zu ignorieren, etwa

    #include <iostream>
    #include <limits>
    
    // ...
    
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    

    Dabei ist std::numeric_limitsstd::streamsize::max() die größte Zahl, die in den Typ std::streamsize passt. Das ganze bedeutet "Ignoriere gaaaaanz viele Zeichen aus std::cin, oder so lange, bis du einen Zeilenumbruch findest, was zuerst kommt."



  • Wie wärs damit

    #include <iostream>
    #include <limits>
    using namespace std;
    
    void ClearStream(std::istream &is)
    {
        is.clear();
        is.ignore(std::numeric_limits<int>::max(), '\n'); 
    }
    
    template <typename T>
    void Input(std::istream& is, T& var)
    {
        is >> var;
        ClearStream(is);
    }
    
    int main()
    {
        int a = 0;
        int b = 0;
        cout << "Geben sie Variable a ein:";
        //Variante 1
        cin >> a;
        ClearStream(std::cin);
        ////////////
        cout << a << '\n';
        cout << "Geben Sie Variable b ein:";
        //Variante 2
        Input(std::cin, b);
        ////////////
        cout << b << '\n';
        return 0;
    }
    

    Beide Varianten unterscheiden sich nicht wirklich, bei der 1. musst du nur selbst nochmal clearen, die 2. übernimmt Eingabe + clearen.



  • Du solltest dabei drauf achten, dass ein progrmam das so arbeitet für die Shell unbenutzbar wird. da hat man gerne, dass man dem programm den Inhalt einer Datei zum fressen gibt in dem die Eingaben vorher eingetragen sind.



  • otze schrieb:

    Du solltest dabei drauf achten, dass ein progrmam das so arbeitet für die Shell unbenutzbar wird.

    weil...?



  • unskilled schrieb:

    otze schrieb:

    Du solltest dabei drauf achten, dass ein progrmam das so arbeitet für die Shell unbenutzbar wird.

    weil...?

    Pipen der Eingaben?



  • Danke fuer eure beiden Loesungen!

    Die Loesungen zum leeren des Inputpuffers sehen beiden vernuenftig aus. Ich verstehe die Bedenken bezueglich der Verwendung von Pipes.
    Ich sehe die Situation so, bei einem Programm, dass ueber den Aufruf gesteuert wird (ls, grep, cat usw.) muss es mit der Pipe arbeiten! Wenn es dagegen ein interaktives Programm ist, ist eine Pipe ja auch nicht sinnvoll.

    Zu meine ersten Frage. Verlasst ihr euch auf die Typsicherheit von C++ und fragt im Programmverlauf den Zustand des Inputstreams ab? Man kann hier ja einen Block mit try/catch absichern und dann, je nach Zustand des Inputstreams reagieren. Also entweder die Eingabefunktion wieder neu aufrufen, oder das Programm mit einem Returncode != 0 beenden?

    Seldon bekommt auch noch wie versprochen seinen *Keks*.

    <edit> nuetzliche Hilfe - http://www.cpp-tutor.de/cpp/le04/cin.html

    PS: Ich habe dann auch schon meine erste Sicherheitsluecke. In meinem Lernprogramm sind die Zahlen ja nicht wirklich zufaellig, mit einer Pipe koennte man cheaten.



  • Meine Augen tun weh, wenn ich diese Website sehe. Es wäre in deinem Interesse, dir ein gutes C++-Buch zuzulegen - Ich empfehle an dieser Stelle immer den C++ Primer, ansonsten ist Accelerated C++ auch sehr beliebt. Beide wurden von Leuten geschrieben, die die Sprache deutlich besser kennen, als jeder Tutorial-Autor des Internets. C++-Tutorials werden so ziemlich ausschließlich von Leuten geschrieben, die C++ nicht gut genug kennen, um zu verstehen, dass es zu komplex ist, um es in einem Tutorial abzuhandeln, und man kann sich mit ihnen sehr schlechte Angewohnheiten einfangen - im konkreten Fall etwa die Benutzung globaler Variablen ohne ersichtlichen Grund.

    Was die Eingabesicherung angeht, da führen viele Wege nach Rom. Bei Aufgabenstellungen dieser Art, wo I/O-Performance von untergeordneter Bedeutung und die Eingabedaten zeilenweise strukturiert sind, ziehe ich ganz gern immer eine Zeile aus dem Stream und parse die dann gesondert, um mich nicht mit dem Failstate des Eingabestreams herumschlagen zu müssen. In deinem Fall sinnvoll wäre vermutlich etwa folgendes:

    #include <iostream>
    #include <sstream>
    #include <stdexcept>
    #include <string>
    
    template<typename T>
    bool read_var(T &dest, std::istream &in = std::cin) {
      std::string line;
    
      if(!std::getline(in, line)) {
        throw std::runtime_error("I/O-Fehler");
      }
    
      std::istringstream parser(line);
      parser >> dest;
    
      return parser;
    }
    
    // ...
    
    int x;
    
    do {
      std::cout << "Zahl eingeben: ";
    } while(!read_var(x));
    

    Das schmeißt dann eine Exception, wenn etwas böse daneben geht, etwa der Eingabestrom in eine Datei umgeleitet wurde und darin keine Eingabedaten mehr vorhanden sind, und gibt ansonsten nur zurück, ob die Eingabe ausgewertet werden konnte, was sich bequem durch erneutes Nachfragen behandeln lässt. Letztendlich ist es aber eine Designfrage - ich kann dir nicht sagen, wie dein Programm sich verhalten soll, das musst du dir schon selbst überlegen.


Anmelden zum Antworten