ifstream :: unget () فشل. هل تنفيذ MS 'عربات التي تجرها الدواب أم أن الكود الخاص بي خاطئ؟

StackOverflow https://stackoverflow.com/questions/3820396

سؤال

بالأمس ، اكتشفت خطأً غريباً في رمز بسيط إلى حد ما يحصل بشكل أساسي على نص من ifstream و askinisting. الكود الذي يفشل فعليًا يقوم بعدد من مكالمات GET ()/PEEK () بحثًا عن الرمز المميز "/*". إذا تم العثور على الرمز المميز في الدفق ، فسيتم استدعاء unge () ، لذا فإن الطريقة التالية ترى أن الدفق يبدأ بالرمز المميز.

في بعض الأحيان ، على ما يبدو يعتمد فقط على طول الملف ، تفشل مكالمة Unget (). داخليا يدعو pbackfail () الذي يعيد ثم 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 على نظام Debian 64bit.

أسئلة:

  1. هل يمكن للآخرين اختبار هذا؟ وسأقدر حقًا بعض أشكال التعاون النشط.
  2. هل هناك اى شى هذا غير صحيح في الكود الذي يمكن أن يسبب المشكلة (لا نتحدث عما إذا كان الأمر منطقيًا)
  3. أو أي أعلام مترجم قد تؤدي إلى هذا السلوك؟
  4. يعد كل رمز المحلل أمرًا ضروريًا للتطبيق ويتم اختباره بشكل كبير ، ولكن لم يتم العثور على هذه المشكلة في رمز الاختبار. هل يجب أن أتوصل إلى حالات اختبار متطرفة ، وإذا كان الأمر كذلك ، فكيف أفعل ذلك؟ كيف يمكن أن أتوقع أن يسبب هذا مشكلة؟
  5. إذا كان هذا بالفعل خطأ ، فأين يجب أن أقوم بإبلاغه بشكل أفضل؟
هل كانت مفيدة؟

المحلول

هل هناك أي شيء غير صحيح في الكود الذي يمكن أن يسبب المشكلة (لا نتحدث عما إذا كان ذلك منطقيًا)

نعم. مطلوب تدفقات قياسية ليكون على الأقل 1 unget() موقع. لذلك يمكنك أن تفعل بأمان واحدة فقط unget() بعد مكالمة إلى get(). عندما تتصل peek() والمخزن المؤقت للإدخال فارغًا ، underflow() يحدث ويقوم التنفيذ بمسح المخزن المؤقت ويحمل جزءًا جديدًا من البيانات. لاحظ أن peek() لا يزيد من موقع الإدخال الحالي ، لذلك يشير إلى بداية المخزن المؤقت. عندما تحاول ذلك unget() يحاول التنفيذ تقليل موضع الإدخال الحالي ، لكنه بالفعل في بداية المخزن المؤقت بحيث يفشل.

بالطبع هذا يعتمد على التنفيذ. إذا كان المخزن المؤقت للتيار يحمل أكثر من حرف ، فقد يفشل في بعض الأحيان وأحيانًا لا. بقدر ما أعرف يخزن تنفيذ Microsoft حرفًا واحدًا فقط في Basic_FileBuf (ما لم تحدد مخزن مؤقتًا أكبر بشكل صريح) ويعتمد على <cstdio> التخزين المؤقت الداخلي (راجع للشغل ، هذا هو أحد الأسباب التي تجعل MVS IoStreams بطيئة). قد يقوم تنفيذ الجودة بتحميل المخزن المؤقت مرة أخرى من الملف عندما unget() فشل. لكن ليس من المطلوب القيام بذلك.

حاول إصلاح الرمز الخاص بك حتى لا تحتاج إلى أكثر من واحد unget() موقع. إذا كنت في حاجة إليها حقًا ، فقم بلف الدفق باستخدام دفق يضمن عدم فشل Unget () (انظر إلى Boost.ioStreams). أيضا الرمز الذي نشرته هو هراء. يحاول unget() وثم get() تكرارا. لماذا ا؟

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top