Domanda

Ieri ho scoperto un bug strano nel codice piuttosto semplice che ottiene in sostanza il testo da un ifstream e tokenizza esso. Il codice che non riesce in realtà fa un certo numero di get () / peek () chiama alla ricerca per il token "/ *". Se il token viene trovato nel flusso, unget () viene chiamato in modo che il metodo successivo vede il flusso di partire con il token.

A volte, apparentemente in funzione solo dalla lunghezza del file, la chiamata unget () non riesce. Internamente chiama pbackfail (), che restituisce EOF. Tuttavia dopo aver eliminato lo stato di flusso, posso tranquillamente leggere altri personaggi quindi non è esattamente EOF ..

Dopo aver scavato in, ecco il codice completo che riproduce con facilità il problema:

#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;
}

Il programma non riesce quando la lunghezza della stringa si avvicina tipici molteplici = di-2 buffersizes 4096, 8192, 12288, ecco l'output:

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

Questo accade quando testato su Windows XP e 7, sia compilato in modalità debug / release, sia dinamico runtime / static, entrambi i sistemi a 32bit e 64bit / compila, tutte con VS2008, compilatore di default / opzioni del linker. Nessun problema trovato durante il test con gcc4.4.5 su un sistema Debian 64 bit.

Domande:

  1. altre persone possono piacere a testare questo? Vorrei davvero apprezzare una qualche forma di collaborazione attiva SO.
  2. è là niente che non è corretto nel codice che potrebbe causare il problema (non parliamo di se ha senso)
  3. o qualsiasi flag di compilazione che potrebbe innescare questo comportamento?
  4. tutto il codice parser è piuttosto critico per l'applicazione e viene testato pesantemente, ma fuori rotta questo problema non è stato trovato nel codice di test. Devo venire con casi di test estremi, e se sì, come posso farlo? Come avrei mai potuto prevedere questo potrebbe causare un problema?
  5. se questo è davvero un bug, dove dovrebbe fare meglio io segnalarlo?
È stato utile?

Soluzione

  

c'è qualcosa che non è corretto nel codice che potrebbe causare il problema (non parlando di se ha senso)

Sì. flussi standard sono tenuti ad avere almeno 1 posizione unget(). Quindi si può tranquillamente fare solo una unget() dopo una chiamata a get(). Quando si chiama peek() e l'ingresso del buffer è vuoto, si verifica underflow() e l'attuazione cancella il buffer e carica una nuova porzione di dati. Si noti che peek() non aumenta posizione corrente di ingresso, in modo che punti all'inizio del buffer. Quando si tenta di unget() i tentativi di implementazione per diminuire la posizione corrente di ingresso, ma è già all'inizio del buffer in modo che non riesce.

Naturalmente questo dipende dall'implementazione. Se il buffer flusso detiene più di un carattere, quindi a volte può fallire e qualche volta no. Per quanto ne so i negozi implementazione Microsoft un solo personaggio in basic_filebuf (a meno che non si specifica una maggiore tampone esplicitamente) e si basa su <cstdio> buffer interno (a proposito, che è uno dei motivi per cui MVS iostreams sono lenti). implementazione qualità può caricare il buffer nuovamente dal file quando unget() fallisce. Ma non è tenuto a farlo.

Prova a correggere il codice in modo che non è necessario più di una posizione unget(). Se si ha realmente bisogno poi avvolgere il torrente con un flusso che garantisce che unget () non mancheranno (sguardo Boost.Iostreams). Anche il codice che avete inviato è una sciocchezza. Si cerca di unget() e poi get() di nuovo. Perché?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top