Frage

Gestern entdeckte ich einen seltsamen Fehler in recht einfachen Code, der im Grunde Text aus einem ifstream bekommt und tokenizes es. Der Code, der tatsächlich ausfällt tut eine Reihe von get () / peek () ruft die Suche nach dem Token „/ *“. Wenn das Token im Stream gefunden wird, unget () aufgerufen wird, so dass die nächste Methode den Strom sieht mit dem Token zu starten.

Manchmal scheinbar nur in Abhängigkeit von der Länge der Datei, die unget () Aufruf fehlschlägt. Intern ruft pbackfail (), die dann EOF zurück. Doch nach dem Stream Zustand Clearing, kann ich glücklich mehr Zeichen lesen, so ist es nicht gerade EOF ..

Nach dem Graben in, hier ist der vollständige Code, der leicht das Problem reproduziert:

#include <iostream>
#include <fstream>
#include <string>

  //generate simplest string possible that triggers problem
void GenerateTestString( std::string& s, const size_t nSpacesToInsert )
{
  s.clear();
  for( size_t i = 0 ; i < nSpacesToInsert ; ++i )
    s += " ";
  s += "/*";
}

  //write string to file, then open same file again in ifs
bool WriteTestFileThenOpenIt( const char* sFile, const std::string& s, std::ifstream& ifs )
{
  {
    std::ofstream ofs( sFile );
    if( ( ofs << s ).fail() )
      return false;
  }
  ifs.open( sFile );
  return ifs.good();
}

  //find token, unget if found, report error, show extra data can be read even after error 
bool Run( std::istream& ifs )
{
  bool bSuccess = true;

  for( ; ; )
  {
    int x = ifs.get();
    if( ifs.fail() )
      break;
    if( x == '/' )
    {
      x = ifs.peek();
      if( x == '*' )
      {
        ifs.unget();
        if( ifs.fail() )
        {
          std::cout << "oops.. unget() failed" << std::endl;
          bSuccess = false;
        }
        else
        {
          x = ifs.get();
        }
      }
    }
  }

  if( !bSuccess )
  {
    ifs.clear();
    std::string sNext;
    ifs >> sNext;
    if( !sNext.empty() )
      std::cout << "remaining data after unget: '" << sNext << "'" << std::endl;
  }

  return bSuccess;
}

int main()
{
  std::string s;
  const char* testFile = "tmp.txt";
  for( size_t i = 0 ; i < 12290 ; ++i )
  {
    GenerateTestString( s, i );

    std::ifstream ifs;
    if( !WriteTestFileThenOpenIt( testFile, s, ifs ) )
    {
      std::cout << "file I/O error, aborting..";
      break;
    }

    if( !Run( ifs ) )
      std::cout << "** failed for string length = " << s.length() << std::endl;
  }
  return 0;
}

Das Programm schlägt fehl, wenn die Stringlänge wird in der Nähe der typischen multiple = von-2 buffersizes 4096, 8192, 12288, hier ist die Ausgabe:

oops.. unget() failed
remaining data after unget: '*'
** failed for string length = 4097
oops.. unget() failed
remaining data after unget: '*'
** failed for string length = 8193
oops.. unget() failed
remaining data after unget: '*'
** failed for string length = 12289

Das geschieht, wenn getestet auf Windows XP und 7, kompilierte sowohl im Debug / Release-Modus, sowohl dynamisch / statisch Laufzeit, sowohl 32-Bit- und 64-Bit-Systeme / compiliert, alle mit VS2008, Standard-Compiler / Linker-Optionen. Kein Problem gefunden, wenn sie mit gcc4.4.5 auf einem 64-Bit-Debian-System zu testen.

Fragen:

  1. können andere Personen bitte dies testen? Ich würde wirklich einige aktive Zusammenarbeit Form schätzen SO.
  2. gibt es alles , die nicht korrekt in dem Code, der das Problem verursachen könnte (nicht zu reden, ob es Sinn macht)
  3. oder beliebige Compiler-Flags, die dieses Verhalten auslösen könnte?
  4. all Code-Parser ist eher kritisch für die Anwendung und wird ausführlich getestet, aber vom Kurs abgekommen dieses Problem gefunden wurde, nicht im Testcode. Soll ich mit extremen Testfällen kommen, und wenn ja, wie mache ich das? Wie könnte ich jemals vorhersagen dies ein Problem verursachen könnte?
  5. , ob dies wirklich ein Fehler ist, wo soll ich am besten Bericht tun?
War es hilfreich?

Lösung

  

es etwas gibt, die nicht korrekt in dem Code, der das Problem verursachen könnte (sprechen nicht darüber, ob es Sinn macht)

Ja. Standard-Streams müssen mindestens 1 unget() Lage haben. So können Sie nur eine unget() nach einem Aufruf get() sicher tun. Wenn Sie peek() und die Eingabe rufen Puffer leer ist, tritt underflow() und die Implementierung löscht den Puffer und lädt einen neuen Teil der Daten. Beachten Sie, dass peek() nicht aktuelle Eingabestelle nicht erhöht, so dass es an den Anfang des Puffers zeigt. Wenn Sie versuchen, die Implementierung versucht, unget() aktuelle Eingabeposition zu verringern, aber es ist schon am Anfang des Puffers so ist es nicht.

Natürlich hängt dies von der Implementierung. Wenn der Strompuffer mehr als ein Zeichen hält, dann kann es manchmal nicht und manchmal nicht. Soweit ich Microsofts Implementierung speichert nur ein Zeichen in basic_filebuf wissen (es sei denn, Sie geben ausdrücklich eine größere Puffer) und stützt sich auf <cstdio> interne Pufferung (btw, dass ein Grund ist, warum MVS iostreams langsam sind). Qualitäts Implementierung kann der Puffer wieder aus der Datei laden, wenn unget() ausfällt. Aber es ist nicht so zu tun, erforderlich.

Versuchen Sie, Ihren Code zu beheben, so dass Sie mehr als eine unget() Position nicht brauchen. Wenn Sie es wirklich brauchen dann wickelt den Strom mit einem Strom, dass garantiert, dass unget () nicht (Blick auf Boost.Iostreams) scheitern. Auch der Code, den Sie geschrieben ist Unsinn. Es wird versucht zu unget() und dann wieder get(). Warum?

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top