ifstream:: unget() терпит неудачу. Ошибочна ли реализация MS или мой код ошибочен?
Вчера я обнаружил странную ошибку в довольно простом коде, который в основном получает текст из ifstream и маркирует его. Код, который фактически терпит неудачу, выполняет ряд вызовов get () / peek (), ищущих маркер "/*". Если токен найден в потоке, вызывается unget (), чтобы следующий метод видел поток, начинающийся с токена.
Иногда, по-видимому, зависящий только от длины файла, вызов unget () завершается неудачей. Внутренне он вызывает pbackfail (), который затем возвращает EOF. Однако после очистки состояния потока я могу с радостью читать больше символов, так что это не совсем EOF..
После копания, вот полный код, который легко воспроизводит проблему:
#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;
}
Программа терпит неудачу, когда длина строки приближается к типичному множеству=из-2 буферизуется 4096, 8192, 12288, вот результат:
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
Это происходит при тестировании на Windows XP и 7, оба компилируются в режиме отладки / выпуска, как динамическая / статическая среда выполнения, как 32-битные, так и 64-битные системы / компиляторы, все с VS2008, параметры компилятора / компоновщика по умолчанию. При тестировании с gcc4.4.5 на 64-битной системе Debian проблем не обнаружено.
Вопросы:
- могут ли другие люди, пожалуйста, проверить это? Я был бы очень признателен за активную форму сотрудничества.
- есть ли что-нибудь неправильное в коде, что может вызвать проблему (не говоря уже о том, имеет ли это смысл)
- или любые флаги компилятора, которые могут вызвать такое поведение?
- all parser код довольно критичен для приложения и тестируется тяжело, но, конечно, эта проблема не была обнаружена в тестовом коде. Должен ли я придумать экстремальные тестовые случаи, и если да, то как это сделать? Как я мог предугадать, что это может вызвать проблемы?
- если это действительно ошибка, где мне лучше всего сообщить об этом?
1 ответ:
Есть ли что-то неправильное в коде, что может вызвать проблему (не говоря уже о том, имеет ли это смысл)
Да. Стандартные потоки должны иметь по крайней мере 1 позицию
unget()
. Таким образом, вы можете безопасно сделать только одинunget()
после вызоваget()
. Когда вы вызываетеpeek()
и входной буфер пуст, происходитunderflow()
, и реализация очищает буфер и загружает новую часть данных. Обратите внимание, чтоpeek()
не увеличивает текущее местоположение входа, поэтому он указывает на начало буфера. При попыткеunget()
реализация пытается уменьшить текущую входную позицию, но она уже находится в начале буфера, поэтому она терпит неудачу.Конечно, это зависит от реализации. Если буфер потока содержит более одного символа, то он может иногда завершаться ошибкой, а иногда и нет. Насколько я знаю, реализация microsoft хранит только один символ в basic_filebuf (если вы не укажете явно больший буфер) и полагается на
<cstdio>
внутренний буферизация (кстати, это одна из причин, почему MVS iostreams медленные). Качественная реализация может снова загрузить буфер из файла при сбоеunget()
. Но этого и не требуется делать.Попробуйте исправить код так, чтобы не требовалось больше одной позиции
unget()
. Если вам действительно это нужно, то оберните поток потоком, который гарантирует, что unget () не потерпит неудачу (посмотрите на Boost.Iostreams). Кроме того, код, который вы опубликовали, - это нонсенс. Он пытаетсяunget()
, а затемget()
снова. Почему?