Scanfs blockieren sich gegenseitig


  • Mod

    scanf("%c",&z);
    

    zu

    scanf(" %c",&z);
    

    So wird eventueller Whitespace (zum Beispiel ein newline nach der letzten Eingabe) überlesen. Ansonsten wird nämlich einfach dieses newline gelesen.

    Ganz wichtig für später: scanf heißt nicht "halte das Programm an, warte bis etwas von der Tastatur eingelesen wurde, dann mach weiter". scanf heißt "interpretiere Zeichen, die auf stdin hereinkommen". Was heißt das, wo ist da der Unterschied? Ein C-Programm ist quasi eine schwarze Kiste, da geht ein Band mit Zeichen rein, das heißt stdin, und zwei Bänder mit Zeichen gehen raus, die heißen stdout und stderr. Das C-Programm weiß nichts von Tastaturen, Bildschirmen, Mäusen, Fenstern, und so weiter*. Seine einzige Verbindung zur Außenwelt sind diese Bänder. Auf den Bändern stehen wirklich nur Zeichen, da steht nicht dabei, wann oder wie die Zeichen auf das Band gekommen sind, keine Farben, nichts. Wie kommen die Zeichen auf das Eingabeband, stdin? Die werden da von der Umgebung (denk dir: Das Betriebssystem) drauf gestanzt. Das Betriebssystem kennt Tastaturen, Mäuse, Bildschirme, usw., das kann damit umgehen und die Zeichen an das Programm weiter geben oder die Zeichen die auf stdout/stderr rauskommen irgendwo ausgeben (z.B. auf dem Bildschirm).
    Warum hält scanf dann manchmal an und manchmal nicht? scanf guckt nur auf die Zeichen, die in stdin stehen. Es guckt das aktuelle Zeichen an und schiebt das Band eins weiter. Wenn da genügend und passende Zeichen kommen, damit das scanf seine Aufgabe erfüllen kann (z.B. "lese Integer") dann ist es fertig. Manchmal steht aber nichts auf dem Band, weil die Umgebung noch keine Zeichen draufgeschrieben hat. Wenn das scanf dann an dem Band zieht, um zum nächsten Zeichen zu kommen, dann muss die Umgebung erst einmal blockieren, bis sie von irgendwoher (z.B. Tastatur) die entsprechenden Zeichen erhält. Das scanf bekommt davon nichts mit, das hat bloß den Befehl zum Ziehen am Band gegeben, die Arbeit, etwas auf das Band zu schreiben, wird woanders gemacht.

    P.S.: Irgendwie etwas wirr geworden und ich bin mir nicht sicher, ob überhaupt rüber kommt, was ich sagen wollte. Bin gerade etwas müde.

    *: es gibt Bibliotheken, die das ändern, sonst wären Computerprogramme recht langweilig. Aber die Standardbibliothek (also scanf, printf & Co.) kennt nur stdin, stdout und stderr.



  • Bullz schrieb:

    Hi,bin neu hier also bitte freundlich sein 🙂 Fange gerade mit dem C programmieren an.

    int x,y;
    char z;
    printf("Gebe erste Zahl ein\n");
    scanf("%i",&x);
    
    printf("Gebe erste Zahl ein\n");
    scanf("%i",&y);
     
    printf("Geben sie Operant ein");
    scanf("%c",&z);  // Obere printf gibt er noch aus aber dann ist das Programm schon beendet. 
    
    return 0
    

    sobald ich an der Stelle ankommen wo er mir einen char einlesen soll bricht das Programm automatisch ab.

    Wenn aber alle scanf %i auskommentierte, dann funktioniert der einzelne scanf %c

    deine scanfs blockieren sich nicht gegenseitig.
    der grund ist, dass sich noch(mindestens) ein zeichen im stream-puffer befindet.
    wenn die eingabe für die beiden ersten scanf korrekt war, dann ist nur noch das newline im zeilenpuffer des streams.
    wenn z.b. 123abc eingegeben wurde, dann sind noch die zeichen abc\n im zeilenpuffer des streams
    (die zeichenfolge bestehend aus zwei zeichen: \n steht für das newline, den zeilenumbruch. es ist eine escapesequenz. http://de.wikipedia.org/wiki/Escape-Sequenz).
    erklärung:

    eine eingabe wird mit einem newline abgeschlossen, mit dem drücken der entertaste. die eingabe samt des newlines landet im zeilenpuffer des streams.
    weil deine scanf funktion "weiß", dass du eine ganzzahl einlesen willst
    (das erkennt sie am inhalt des formatstrings, http://en.wikipedia.org/wiki/Scanf_format_string
    den du an scanf übergibst, in deinem fall ist dein formatstring zum einlesen der integerwerte also: "%i"),
    liest und prüft sie zeichen für zeichen, dass sich im puffer befindet und stoppt den einlesevorgang, wenn das zeichen im puffer nicht zum formatstring passt. war die eingabe korrekt, stoppt scanf den einlesevorgang beim newline, also beim zeichen \n. war die eingabe nicht korrekt, z.b. bei der eingabe von 123abc, stoppt scanf den einlesevorgang beim einlesen des zeichens 'a'. das zeichen wird im puffer gelassen und es wird lediglich 123 ausgewertet, konvertiert und in der integervariable, deren adressse du als parameter an scanf übergeben hast gespeichert.
    es ist z.b. auch möglich, dass der benutzer abc, also keine zahl eingibt, dann wird nichts ausgewertet und die zeichen abc verbleiben im zeilenpuffer des streams. in diesem fall gibt scanf 0 zurück. es ist also sehr(!) angebracht, den rückgabewert von scanf zu prüfen, um entsprechend im programm reagieren zu können, z.b. mit der aufforderung einer neuen eingabe.

    überlegen wir uns also mal kurz, was im verlauf deines programms passiert, wenn der benutzer bei der ersten aufforderung z.b. abc an scanf übergibt:
    scanf merkt, ahaaaaa, für mich gibt es nichts zu tun und lässt abc im puffer stehen, der variable x wird kein wert zugewiesen. kein wunder.
    das programm fährt fort und die zweite scanf funktion wird aufgerufen. auch hier merkt scanf, ohoooo, nix zu tun für mich! spuckt das 'a' auch wieder in den puffer zurück und das programm fährt fort. es ist nach wie vor abc im gepufferten eingabestrom. nun sind wir auch schon fast durch. nach der aufforderung den operanden einzugeben kommt scanf erneut ins spiel. dieses mal findet die funktion gefallen daran was sie vorfindet, denn die zeichenfolge abc befindet sich ja immer noch im zeilenpuffer(gääähn ... sorry :p) und an erster stelle im zeilenpuffer steht ja das zeichen 'a'. also liest scanf das zeichen 'a' brav ein, weil es ja zum formatstring("%c") passt (tadaaa! :-)) die zeichen bc(und das newline) bleiben im puffer und das programm ist zu ende.

    klar geworden ist(oder wird es früher oder später) hoffentlich(werden): in diesem und in einer beliebigen anzahl anderer fälle einer falscheingabe würde dir

    scanf(" %c",&z);
    

    rein gar nichts nützen!

    das programm steht daher auf verdammt wackeligen beinen! 😞
    du musst hoffen, dass der benutzer ja nichts verkehrt eingibt(wenn du möchtest, dass dein programm ordentlich funktioniert. du könntest ja auch der meinung sein, wer scheiße eingibt, bekommt scheiße. ok. das ist dir überlassen.)!
    ansonsten, wenn dein programm vernünftig und stabil funktionieren soll, müssen also diverse maßnahmen nach einer eingabe folgen. dann brauchst du auch nicht zu hoffen, dass der benutzer ja nichts bei der eingabe falsch macht.
    eine maßnahme ist, den rückgabewert von scanf zu prüfen.
    scanf gibt die anzahl der erfolgreich konvertierten elemente zurück.

    Return Value
    On success, the function returns the number of items of the argument list successfully filled. This count can match the expected number of items or be less (even zero) due to a matching failure, a reading error, or the reach of the end-of-file.

    http://www.cplusplus.com/reference/cstdio/scanf/

    das allein reicht aber leider nicht aus, denn bei einer falscheigabe(z.b: abc, 123abc, etc) sind ja die unerwünschten zeichen abc immer noch im puffer!
    die im puffer übrig gebliebenen zeichen inklusive des newlines kannst du mit einer schleife verwerfen:

    while((c = getchar()) != '\n' && c != EOF){}
    

    jedes zeichen, dass sich noch im puffer befindet, wird einschließlich des \n per getchar eingelesen und damit verworfen.
    idealerweise lagerst man die schleife in eine funktion aus:

    void clear_streambuffer(void)
    {
    	int c;
    	while((c = getchar()) != '\n' && c != EOF){}
    }
    

    diese funktion mit der schleife ist aber keine eierlegende wollmilchsau!
    bedenke: beim aufruf dieser funktion wird auf eine eingabe gewartet, wenn sich
    kein zeichen im puffer befinden!

    mfg
    der kenner der c streams
    🙂



  • Die Funktion clear_streambuffer macht nicht was der Name suggeriert.
    Das ist eher ein discard_to_endofline

    Denn eine Fehleingabe durch die Entertaste wird damit nicht abgefangen,
    da es nur bis zum nächsten '\n' liest. Alle Zeichen danach bleiben noch im Eingabestrom.
    Also doch nicht gelöscht.


  • Mod

    Also meine Erklärung von Streams war ja schon schlecht geschrieben, aber die von "kenner der c streams" ist schlichtweg falsch. Zeilenpuffer? Den Unsinn hatten wir doch neulich schon einmal und den gibt es schlichtweg nicht. Daher macht die Funktion auch nicht, was versprochen wird. Puffer, Zeilen, Tasten, all das gibt es in dem C-Programm nicht. Das ist manchmal Teil der Betriebsumgebung, dann und nur dann, wenn das Programm in einer Konsole mit Verbindung zu einer Tastatur läuft. Das C-Programm bekommt davon gar nichts mehr mit, auch wenn das Gesamtverhalten der Einheit Konsole+Tastatur+Programm sich scheinbar so verhält als wäre da ein Zeilenpuffer. Der ist aber nicht da und wenn man so programmiert als wäre er doch da, so wird es schon an kleiner Änderung der genannten Konfiguration nicht mehr funktionieren. Nicht einmal wenn man diese Einheit benutzt funktioniert es richtig, siehe DirkBs Einwand. Etwas weiteres, wo das trotz Konsole+Tastatur nicht funktioniert ist, wenn sehr schnell getippt wird, zum Beispiel mittels Copy&Paste.



  • DirkB schrieb:

    Die Funktion clear_streambuffer macht nicht was der Name suggeriert.
    Das ist eher ein discard_to_endofline

    Denn eine Fehleingabe durch die Entertaste wird damit nicht abgefangen,
    da es nur bis zum nächsten '\n' liest. Alle Zeichen danach bleiben noch im Eingabestrom.
    Also doch nicht gelöscht.

    bitte genau lesen.
    das irgendwas gelöscht wird, habe ich nicht geschrieben.
    ich habe auch nicht geschrieben, dass eine fehleingabe abgefangen wird.
    ich habe geschrieben, dass die zeichen aus dem puffer eingelesen und verworfen werden, inklusive des newlines.
    dein argument, dass danach alle zeichen im eingabestrom bleiben ist nicht unbegründet, denn wir wissen nicht wie der puffer aussieht. das ist aus der sicht des programms aber uninteressant, denn das programm "sieht" einen leeren puffer, beim nächsten scanf wartet das programm auf eine eingabe.

    SeppJ schrieb:

    Also meine Erklärung von Streams war ja schon schlecht geschrieben, aber die von "kenner der c streams" ist schlichtweg falsch. Zeilenpuffer? Den Unsinn hatten wir doch neulich schon einmal und den gibt es schlichtweg nicht. Daher macht die Funktion auch nicht, was versprochen wird. Puffer, Zeilen, Tasten, all das gibt es in dem C-Programm nicht.

    puffer gibt es nicht? herzlichen glückwunsch, sie haben den zonk gewonnen!
    das ist falsch! natürlich gibt es puffer!

    das streams gepuffert ist, steht in vielen tutorials und büchern

    High-level file access structures the file access through memory buffers.
    These buffers hold data going to or coming from the file.

    http://www-control.eng.cam.ac.uk/~pcr20/C_Manual/chap13.html

    hier eine übersicht verschiedener pufferstrategien:
    http://www.gnu.org/software/libc/manual/html_node/Stream-Buffering.html

    man kann die pufferung sogar beeinflussen:
    http://www.gnu.org/software/libc/manual/html_node/Controlling-Buffering.html#Controlling-Buffering

    das streams gepuffert sind, steht sogar im (c open)standard:

    7.19.3 Files
    At program startup, three text streams are predefined and need not be opened explicitly
    — standard input (for reading conventional input), standard output (for writing
    conventional output), and standard error (for writing diagnostic output). As initially
    opened, the standard error stream is not fully buffered; the standard input and standard
    output streams are fully buffered
    if and only if the stream can be determined not to refer
    to an interactive device.


  • Mod

    Dieser Puffer macht ganz was anderes als dieser ominöse "Zeilenpuffer", den du beschreibst. Selfowned.

    ich habe geschrieben, dass die zeichen aus dem puffer eingelesen und verworfen werden, inklusive des newlines.

    Und das passiert eben gerade nicht. Deine Funktion verwirft Zeichen aus dem Stream, bis '\n' oder EOF. Nix mit Puffer. Stehen aktuell keine Zeichen im Stream, so wird ggf. gewartet, bis welche kommen. Stehen zwei oder mehrere newlines direkt zur Verfügung (wie du sagen würdest "im Puffer"), wird das erste verworfen und dann ist Schluss. Denn da ist (von der Programmlogik her) kein Puffer. Nur ein Stream.

    Dieser Puffer den du zitierst ist eine interne Optimierung der Streamfunktionen und hat mit der Programmlogik genau 0 zu tun. Alle Funktionen würden 100% gleichartig auf einem ungepufferten Stream funktionieren, es wäre bloß eventuell etwas lahmer.



  • SeppJ schrieb:

    Deine Funktion verwirft Zeichen aus dem Stream, bis '\n' oder EOF. Nix mit Puffer. Stehen aktuell keine Zeichen im Stream, so wird ggf. gewartet, bis welche kommen. Stehen zwei oder mehrere newlines direkt zur Verfügung (wie du sagen würdest "im Puffer"), wird das erste verworfen und dann ist Schluss. Denn da ist (von der Programmlogik her) kein Puffer. Nur ein Stream.

    nix mit puffer? lol! klar mit puffer. weil der stream gepuffert ist, also werden die sich im puffer befindenden zeichen eingelesen.

    stehen zwei oder mehr newlines zur verfügung ... das ist ein argument lediglich theoretischer natur.

    When a stream is fully buffered,
    characters are intended to be transmitted to or from the host environment as a block when
    a buffer is filled. When a stream is line buffered, characters are intended to be
    transmitted to or from the host environment as a block when a new-line character is
    encountered.

    siehst du, darum wirst du in einem gepufferten stream keine zwei oder mehr newlines auf einmal vorfinden.
    und das hat jede menge mit logik zu tun. kenne ich das verhalten der streams, kann ich entsprechend logische programmabläufe bilden.



  • kenner der c streams schrieb:

    siehst du, darum wirst du in einem gepufferten stream keine zwei oder mehr newlines auf einmal vorfinden.

    Klar geht das.

    Du gehst in deinem Beispiel doch von einer fehlerhaften Eingabe aus.

    kenner der c streams schrieb:

    ... wenn z.b. 123abc eingegeben wurde...

    Und wenn da zweimal (oder noch öfter) die Entertaste gedrückt wurde, stehen diese '\n' noch im Eingabestrom.
    Dein clear_streambuffer liest davon auch nur eins ein.
    Wenn du Falscheingaben abfangen willst, musst du mehr machen.

    Und es ist völlig egal ob da ein Puffer ist, denn du hast
    - keinen anderen Zugriff darauf als das Auslesen.
    - keine Informatiun ob überhaupt ein Zeichen vorhanden ist.
    - keine Information über das Alter der Eingabe.
    - Du kannst ihn nicht löschen.

    Klar bieten Betriebssysteme solche Funktionen an.
    Aber mit den Mitteln aus dem C-Standard geht das nicht.



  • Also bleibt alles in allem nur

    while((c = getchar()) != '\n' && c != EOF){}
    

    oder das verhasste

    fflsuh(stdin)
    

    übrig für den Anfänger?


  • Mod

    kenner der c streams schrieb:

    nix mit puffer? lol! klar mit puffer. weil der stream gepuffert ist, also werden die sich im puffer befindenden zeichen eingelesen.

    Und wenn da nix ist? Oder mehr als du denkst? Hier kann ich schon aufhören, denn der Rest deines Arguments beruht ja auf der Fehlannahme, dass das irgendeinen Unterschied machen würde. Jedoch macht es keinen, wie du leicht an Testprogrammen feststellen kannst.



  • DirkB schrieb:

    Du gehst in deinem Beispiel doch von einer fehlerhaften Eingabe aus.

    kenner der c streams schrieb:

    ... wenn z.b. 123abc eingegeben wurde...

    richtig!

    DirkB schrieb:

    Und wenn da zweimal (oder noch öfter) die Entertaste gedrückt wurde, stehen diese '\n' noch im Eingabestrom.

    tun sie nicht.
    nach dem ersten drücken der entertaste ist scanf fertig. das programm fährt fort und es wird die schleife ausgeführt, welche die falscheingabe verwirft.
    es wird also nur der erste druck der enter-taste berücksichtigt.

    SeppJ schrieb:

    Und wenn da nix ist?

    hatte ich schon geschrieben.
    wenn da nichts ist, wird auf eine eingabe gewartet.
    hättst du selbst lesen können, aber bitte, für dich extra noch einmal:

    kenner der c streams schrieb:

    diese funktion mit der schleife ist aber keine eierlegende wollmilchsau!
    bedenke: beim aufruf dieser funktion wird auf eine eingabe gewartet, wenn sich
    kein zeichen im puffer befinden!

    SeppJ schrieb:

    Oder mehr als du denkst?

    hatte ich auch schon geschrieben, als antwort auf deinen vorigen beitrag.
    das such bitte selbst, ich zitiere mich nicht noch einmal selbst.


  • Mod

    beginner88888 schrieb:

    Also bleibt alles in allem nur

    while((c = getchar()) != '\n' && c != EOF){}
    

    oder das verhasste

    fflsuh(stdin)
    

    übrig für den Anfänger?

    Kommt drauf an, wofür. Beide machen ganz was unterschiedliches. Das Erste (vom Standard gedeckt) macht, wie es DirkB so schön ausdrückt:

    Die Funktion clear_streambuffer macht nicht was der Name suggeriert.
    Das ist eher ein discard_to_endofline

    Wohingegen das Zweite (nicht vom Standard gedeckte Erweiterung von Microsoft) tatsächlich auf diesem internen Puffer arbeitet, von dem der Unregistrierte Nutzer hier meint, man könne da drauf zugreifen. Dies ist tatsächlich solch eine Möglichkeit, aber das ist ja auch kein standardkonformes C.



  • beginner88888 schrieb:

    Also bleibt alles in allem nur

    while((c = getchar()) != '\n' && c != EOF){}
    

    genau das ist meine empfehlung.

    beginner88888 schrieb:

    oder das verhasste

    fflsuh(stdin)
    

    übrig für den Anfänger?

    das hat laut standard undefiniertes verhalten, funktioniert aber bei diversen os(compilerbedingt) wie z.b. windows.



  • SeppJ schrieb:

    von dem der Unregistrierte Nutzer hier meint, man könne da drauf zugreifen.

    ja, das kann man.
    die standardfunktionen getchar, scanf, etc. können das. sie greifen darauf zu und lesen daraus ein.


  • Mod

    kenner der c streams schrieb:

    DirkB schrieb:

    Und wenn da zweimal (oder noch öfter) die Entertaste gedrückt wurde, stehen diese '\n' noch im Eingabestrom.

    tun sie nicht.
    nach dem ersten drücken der entertaste ist scanf fertig. das programm fährt fort und es wird die schleife ausgeführt, welche die falscheingabe verwirft.
    es wird also nur der erste druck der enter-taste berücksichtigt.

    Sachlich falsch. Kannst du an Testprogrammen prüfen. Diskussion zu Ende?

    diese funktion mit der schleife ist aber keine eierlegende wollmilchsau!
    bedenke: beim aufruf dieser funktion wird auf eine eingabe gewartet, wenn sich
    kein zeichen im puffer befinden!

    Soso. Also wohl doch keine Pufferleerung, sondern eine ganz normale Eingabefunktion?

    hatte ich auch schon geschrieben, als antwort auf deinen vorigen beitrag.

    Bloß war das "siehst du, darum wirst du in einem gepufferten stream keine zwei oder mehr newlines auf einmal vorfinden. " schlicht falsch. Also wohl doch nix mit Puffern.

    Da ich mir ziemlich sicher bin, dass du der gleiche bist, der das Thema vor ein paar Wochen schon einmal diskutiert hat, spare ich mir mal die Gegenbeweisprogramme und weitere Argumente. Da du BEWEISE damals schon ignoriert hast, ist dir nicht zu helfen oder es ist absichtliche Trollerei. Falls du interessiert bist, ist das auch leicht selber zu testen.

    @all: Falls den Typ irgendjemand ernst nimmt: Bitte melden. Dann konstruiere ich ein Gegenbeispiel.



  • SeppJ schrieb:

    @all: Falls den Spinner irgendjemand ernst nimmt: Bitte melden. Dann konstruiere ich ein Gegenbeispiel.

    ach, jetzt werden wir auch noch beleidigend?
    immer schön sachlich bleiben, es sei denn du bist scharf darauf dir selbst armutszeugnisse auzustellen.
    in dem fall darfst du gern so weiter machen 🙂

    "beweise" ?!
    du meinst also dieses "gegenbeispiel":
    http://ideone.com/oEMEPG
    zeile 7 liest und verwirft das erste newline, zeile 8 liest und verwirft das zweite newline.
    in zeile 10 und 11 wird in einer schleife genau das ausgegeben, was eingegeben wird. es wird zeichen für zeichen in den puffer geschrieben und sofort aus dem puffer gelesen und angezeigt. normales programmverhalten.
    wo ist das jetzt ein gegenbeispiel?



  • Da braucht es kein anderes Programm, sondern nur eine andere Eingabe.

    Und nach einem Druck auf die Enter-Taste, so wie die Aufforderung in dem Programm ist, ist das Programm nicht zu Ende.
    Da muss man mindestens noch eine beliebige Taste und Enter drücken.
    👎



  • DirkB schrieb:

    Da braucht es kein anderes Programm, sondern nur eine andere Eingabe.

    Und nach einem Druck auf die Enter-Taste, so wie die Aufforderung in dem Programm ist, ist das Programm nicht zu Ende.
    Da muss man mindestens noch eine beliebige Taste und Enter drücken.
    👎

    selbstverständlich ist das programm nicht zu ende!
    warum sollte das auch zu ende sein?!
    das ist doch nicht mein beispiel!
    hab ich doch auch schon längst geschrieben, das die besagte schleife auf eine eingabe wartet, wenn nix im eingabestrom ist!


Anmelden zum Antworten