mikrocontroller.net

Forum: PC-Programmierung [Linux] Eingabepuffer leeren in C


Autor: Christoph S. (mixer) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

da es den Befehl "fflush(stdin);" in Linux ja nicht gibt bzw. er keine 
Auswirkung hat suche ich jetzt eine Möglichkeit den Eingabepuffer zu 
leeren um mit scanf/getchar dann einzelne Zeichen bzw. Strings sauber 
einlesen kann.

Momentan arbeite ich mit
while( getchar() != '\n' )

Allerdings muss ich dann zweimal Enter drücken. Bei Windows mit der 
fflush-Funktion brauche ich beim gleichen Code nur ein Enter.

Gibt es eine einfachere/bessere Möglichkeit den Eingabepuffer komplett 
zu leeren??

MFG Mixer

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was hast du vor?
Für etwas ähnliches wie ein Konsolenprogramm mit Menüsteuerung ist
die Standardeingabe eher ungeeignet.

Autor: Christoph S. (mixer) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klaus Wachtler schrieb:
> Was hast du vor?
> Für etwas ähnliches wie ein Konsolenprogramm mit Menüsteuerung ist
> die Standardeingabe eher ungeeignet.

Sowas habe ich aber vor. Was soll ich dann stattdessen nehmen??

Autor: Thomas Pircher (tpircher) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
fflush() ist ISO C90. Warum sollte es diese Funktion ausgerechnet unter 
Linux nicht geben? fflush() schreibt alle _Ausgabe-Daten und duerfte 
deshalb keinen Effekt auf stdin haben.

Was du suchst ist fpurge(), das ist aber keine Standard-Funktion. Wenn 
es standard-konform sein muss, dann wuerde ich mir mit fileno() den 
filedescriptor holen und dann mit read() alle Daten lesen.
Um abzubrechen wenn keine daten mehr im File sind, wurder ich entweder 
select() nehmen oder den fd non-blocking setzen.

Ansonsten koenntest du eine Funktion von ncurses verwenden(?)

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Meine Lösung für solche Fälle ist _getch(). _kbhit() (conio.h
mit diesen Funktionen gibt es auch unter DOS/Windows); weil man
es für Quelltexte aus der Windowswelt öfter braucht, ist Sleep()
auch gleich mit dabei, auch wenn es dort nicht zu conio.h gehört.
Wer es also etwas eleganter will, wirft Sleep() hier raus und
packt sie in die richtige Headerdatei.

_kbhit() liefert true, wenn ein Zeichen geholt werden kann, ohne
zu blockieren.

_getch() liefert ein Zeichen, oder im Fehlerfall -1.

_putch() gibt ein Zeichen aus.

Sleep() wartet die angegebene Zahl msec.


Eine Headerdatei conio.h:
#ifndef __CONIO_H_
#define __CONIO_H_

//////////////////////////////////////////////////////////////////////
//
// Sleep(), _kbhit(), und _getch() fuer __unix__.
//


#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>

#include <termios.h>
#include <unistd.h>

#include <sys/select.h>
#include <sys/types.h>



/* Sleep() wartet die angegebene Anzahl Millisekunden (im Gegensatz
 * zu ANSI-sleep(), das kennt nur ganze Sekunden).
 */
void Sleep( int msec );

// _kbhit() liefert true, wenn mindestens ein Zeichen im
// Tastaturpuffer steht
int _kbhit( void );

// liefert ein Zeichen von der Tastatur (oder EOF bei Fehler)
int _getch( void );

// gibt ein Zeichen aus
void _putch( char c );

//
// Ende von Sleep(), _kbhit(), und _getch() fuer __unix__.
//
//////////////////////////////////////////////////////////////////////

#endif /* ifndef __CONIO_H_ */

Quelltext conio.c jeweils mitkompilieren:
//////////////////////////////////////////////////////////////////////
//
// Sleep(), _kbhit(), und _getch() fuer __unix__.
//

#include "conio.h"


/* Sleep() wartet die angegebene Anzahl Millisekunden (im Gegensatz
 * zu ANSI-sleep(), das kennt nur ganze Sekunden).
 */
void Sleep( int msec )
{
  struct timeval
    timeout;

  timeout.tv_sec  = msec/1000;
  timeout.tv_usec = msec%1000;

  select( 0, NULL, NULL, NULL, &timeout );
}

// In einer struct termios wird eine Terminalkonfiguration
// gespeichert.
// Davon brauchen wir zwei: eine fuer den Originalzustand,
// und eine zum Aendern.
static struct termios     term_neu,
                          term_original;

// Darin wird die Aenderung der Terminaleinstellungen gemerkt,
// damit sie nur einmal stattfinden:
static int                _conio_initialisiert = 0;

// Bei Programmende soll automatisch die Originalkonfiguration
// wiederhergestellt werden:
static void _exit_conio( void )
{
  if( _conio_initialisiert )
    {
      tcsetattr( 0, TCSANOW, &term_original );
      _conio_initialisiert = 0;
    }
}

// Diese Funktion macht beim ersten Aufruf folgendes:
// - Merken der Originalkonfiguration von stdin (file id=0)
// - Installieren von _exit_conio() mit atexit()
// Bei jedem Aufruf zudem:
// - Ausschalten von ECHO und ICANON (u.a. Zeilenpufferung)
static void _init_conio( void )
{
  if( !_conio_initialisiert )
    {
      //  Dies passiert nur beim ersten Aufruf

      // Originalkonfiguration holen:
      tcgetattr( 0, &term_original );  // 0 ist stdin
      // komplett kopieren:
      term_neu = term_original;
      // ICANON und ECHO ausschalten:
      term_neu.c_lflag = term_neu.c_lflag & ~(ICANON | ECHO);
      // Bei Programmende wieder aufraeumen lassen:
      atexit( _exit_conio );
      // In _conio_initialisiert merken, dass wir schon etwas getan
      // haben:
      _conio_initialisiert++;
    }

  // bei jedem Aufruf die eigene Terminalkonfiguration
  // fuer stdin (file id = 0) passend setzen:
  tcsetattr( 0, TCSANOW, &term_neu );
}

// _kbhit() liefert true, wenn mindestens ein Zeichen im
// Tastaturpuffer steht
int _kbhit( void )
{
  fd_set
    lesen_set;

  struct timeval
    timeout;

  _init_conio();

  FD_ZERO( &lesen_set );
  FD_SET( 0, &lesen_set ); // 0 ist std-Eingabe

  timeout.tv_sec  = 0;
  timeout.tv_usec = 0;

  if( select( 1,
              &lesen_set,  // set fuer "Lesen"
              NULL,        // kein set fuer "Schreiben"
              NULL,        // kein set fuer "out of band data"
              &timeout
              )
      ==-1
      )
      {
        // select macht Bloedsinn!
        _exit_conio();
        return 0;
      }
  else
    {
      // in lesen_set steht jetzt an der Stelle [0], ob gelesen
      // werden kann:
        _exit_conio();
      return FD_ISSET( 0, &lesen_set );
    }
}

// liefert ein Zeichen von der Tastatur (oder EOF bei Fehler)
int _getch( void )
{
  char c;
  int ret;

  _init_conio();

  ret = read( 0, &c, 1 );

  _exit_conio();
  return ( ret==1 ) ? (int)(unsigned char)c : EOF;
}

// gibt ein Zeichen aus
void _putch( char c )
{
  write( 1, &c, 1 );
}


//
// Ende von Sleep(), _kbhit(), und _getch() fuer __unix__.
//
//////////////////////////////////////////////////////////////////////

Beispielprogramm: liest Zeichen ein und gibt jeweils ihrem ASCII-Wert
aus, bis man auf Esc drückt:
#include "projekte/conio.h"


int main( int nargs, char **args )
{
  int c;
  char puffer[100];
  int iZeichen;

  do
  {
    c = _getch();
    sprintf( puffer, "das Zeichen ist dezimal %d", c );
    for( iZeichen=0; iZeichen<sizeof(puffer)&&puffer[iZeichen]; ++iZeichen )
    {
      _putch( puffer[iZeichen] );
    }
    _putch( '\n' );
  }
  while( c!=0x1b );

  return 0;
}

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
PS:
Das Ganze funktioniert in einem Konsolenfenster (xterm etc.)
ebenso wie auf der Textkonsole.

Autor: Christoph S. (mixer) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

Danke für eure Tips!!

Habs jetzt mal schnell mit
__fpurge(stdin);
probiert und es sieht gut aus!!

MFG Mixer

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wieso wollen alle eigentlich immer fflush dafür mißbrauchen? fflush 
dient dazu, den Inhalt des Puffers an sein Ziel zu schreiben  (und nicht 
etwa zu verwerfen, wie viele zu glauben scheinen) , was natürlich nur 
für Ausgangspuffer einen Sinn ergibt.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Deswegen hat es ja bei stdin auch keinen Sinn. So wie ich das verstanden 
habe.

Hatte aber letztens das Problem, dass ich perror vor einem printf 
aufgerufen hab und der Text von perror stand NACH dem von printf. Da 
half nur ein fflush(stdout).
Verstehe aber selber nicht so genau warum das in dem Fall verdreht wird. 
Wusste nur, dass man solche Phänomenen damit entgegnen kann.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Deswegen hat es ja bei stdin auch keinen Sinn. So wie ich das verstanden
> habe.

Richtig.

> Hatte aber letztens das Problem, dass ich perror vor einem printf
> aufgerufen hab und der Text von perror stand NACH dem von printf.

War's nicht anders herum? Eigentlich sollte perror (stderr) ungepuffert 
direkt ans Terminal raus, während stdout puffert und der Text daher 
evtl. erst später ankommt.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
nicht andersrum?
erst printf aufgerufen dann perror, aber Ausgabe in der anderen 
Reihenfolge?

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
War eine Schleife, also ist quasi vor dem perror auch nach dem perror. 
Kann also sein, dass es auch mit den printfs davor zusammenhing. :-)
Aber warum ist perror nicht der Einfachheit halber auch gepuffert? Ist 
doch blöd wenn man sich da noch als Anwender einer API um 
Puffersynchronisierung kümmern muss.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Simon K. schrieb:
> War eine Schleife, also ist quasi vor dem perror auch nach dem perror.
> Kann also sein, dass es auch mit den printfs davor zusammenhing. :-)
> Aber warum ist perror nicht der Einfachheit halber auch gepuffert?

Weil es erlaubt ist, dass eine normale Ausgabe durchaus auch eine halbe 
Stunde im Ausgabebuffer steht.
Bei einer Fehlermeldung ist es aber unter Umständen mehr als fatal, wenn 
sie durch das I/O System verzögert wird.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Simon K. schrieb:
>> War eine Schleife, also ist quasi vor dem perror auch nach dem perror.
>> Kann also sein, dass es auch mit den printfs davor zusammenhing. :-)
>> Aber warum ist perror nicht der Einfachheit halber auch gepuffert?
>
> Weil es erlaubt ist, dass eine normale Ausgabe durchaus auch eine halbe
> Stunde im Ausgabebuffer steht.
> Bei einer Fehlermeldung ist es aber unter Umständen mehr als fatal, wenn
> sie durch das I/O System verzögert wird.

Halbe Stunde? Ich dachte der Sinn von dem Puffer in dem Fall ist, dass 
immer gesammelt wird und spätestens alle X Millisekunden das als ein 
Haufen an das System übermittelt wird um den Aufruf Overhead zu 
verringern. Bzw das Verhältnis von Daten zu Overhead zur erhöhen.

Autor: Klaus Wachtler (mfgkw)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es wird nicht alle soundsoviel msec ausgegeben, sondern defaultmäßig
immer bei einem \n.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Simon K. schrieb:
> Halbe Stunde? Ich dachte der Sinn von dem Puffer in dem Fall ist, dass
> immer gesammelt wird und spätestens alle X Millisekunden das als ein
> Haufen an das System übermittelt wird

Wie soll das auf einem Nicht-Multitasking System gehen?

Üblich ist, dass der Buffer bei einem bestimmten Füllgrad bzw beim 
Auftreten eines \n geleert wird (oder durch einen fflush)
Wenn ich 2 Zeichen (ohne \n) in den Buffer stelle und dann eine halbe 
Stunde keinen printf mache, dann können die beiden Zeichen durchaus eine 
halbe Stunde im Ausgabebuffer warten, bis ich sie dann endlich mit einem 
\n erlöse.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Aber warum ist perror nicht der Einfachheit halber auch gepuffert?

Es ist nicht speziell perror, sondern generell alles, was nach stderr 
geht. Das wird deshalb gemacht, weil dahin oft eben Debug- oder 
Fehlerausgaben geschrieben werden, bevor ein Programm abschmiert, und 
dann will man, daß diese Ausgaben auch sicher ankommen und nicht 
irgendwo im Puffer versickern, denn der verschwindet zusammen mit dem 
Programm.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.