昨天我在相当简单的代码中发现了一个奇怪的错误,它基本上从 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;
}
当字符串长度接近典型的 multiple=of-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,默认编译器/链接器选项。在 64 位 Debian 系统上使用 gcc4.4.5 测试时没有发现问题。
问题:
- 其他人可以测试一下吗?我真的很感激一些积极的合作形式。
- 代码中是否有任何不正确的地方可能导致问题(不讨论是否有意义)
- 或任何可能触发此行为的编译器标志?
- 所有解析器代码对应用程序都相当关键,并且经过大量测试,但是当然,在测试代码中没有发现这个问题。我是否应该提出极端的测试用例,如果是,我该怎么做?我怎么能预测这会导致问题?
- 如果这真的是一个错误,我应该在哪里最好地报告它?