На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> fflush( stdin ) в разных версиях Visual Studio (2010 vs 2019)
    Всем хай! Сходу к делу!

    Есть код, который запрашивает ввод данных с клавиатуры. Известная проблема, что при вводе строковых данных начинает ввод проскакивать (пропускать). Спасает обычно fflush( stdin ), который очищает буффер потока.

    Недавно была такая проблема. Есть такой код (псевдо):
    ExpandedWrap disabled
      ввод целое
      вводе строка
      ввод дробное
      ввод строка
      ввод строка


    чтобы не париться, поставил после каждого ввода fflush( stdin );
    ExpandedWrap disabled
      ввод целое
      fflush( stdin );
       
      ввод строка
      fflush( stdin );
       
      ...

    все отлично работает. Этот код в Visual Studio 2010 запускается.

    Запускается под Visual Studio 2019 - ввод проскакивает!! fflush( stdin ) - НЕ спасает. Причем только в одном месте, между вводом двух строковых данных. Т е свою функцию fflush( stdin ) перестал выполнять. Какой стандарт в студии 2019 года - я не знаю, наверное, на С89, а постарше.
    В итоге пришлось победить эту проблему тупо вставив getchar(); и все стало ок.

    Вопрос: как побеждать правильно проскакивание ввода данных в студии 2019 года??

    спс. за внимание

    Добавлено
    Речь про код языка "чистый" Си (никакого С++, ни микрона, хотя в С++ что-то подобное было и там локально как-то побеждалось cin.get())
    Сообщение отредактировано: FasterHarder -
      Насколько я изучил ситуацию, эта разница в поведении неустранима. Стандарт вообще не определяет поведение fflush() для входных потоков, только для выходных, для которых выполняется сброс кешированных данных в поток. Что до MS, то насколько я могу судить, она попыталась реализовать поведение fflush(), совместимое с POSIX. Однако если погуглить, многие специалисты даже в POSIX не рекомендуют использовать fflush() для потоков ввода. Причина в том, что это зачастую приводит к непредсказуемым результатам из-за неконтролируемой программным методом величины отбрасываемого ввода. Который в свою очередь зависит от внутренних алгоритмов буферизации стандартных функций ввода/вывода и возможно даже механизмов буферизации ОС.
      Можно сымитировать fflush() посредством setvbuf(). Попробуй setvbuf(stdin, buf, _IOLBF, sizeof(buf)) при каком-нибудь char buf[64]. По идее установка своего буфера также должна сбрасывать любую имеющуюся буферизацию.
        Qraizer, круто!
        еще ряд уточнений:
        1. В студии 2010 можно спокойно использовать fflush( stdin ) и НИЧЕГО не бояться?
        2. Почему для буфера берешь длину 64, а не 63, например, или 38? 64 = 8 * 8 или 8байт, может с этим как-то связано или что-то типа около того...

        В итоге сделал, как ты написал (правда тестил в 2010) и ничего не проскакивает.

        Вот на этом коде тестировал (проскакивало после ввода числа):
        ExpandedWrap disabled
          #include <stdio.h>
          #include <stdlib.h>
           
          int main( void )
          {
              char* s = (char* )malloc( 100 );
              size_t x;
              printf( "Input string: " );
              gets( s );
              
              printf( "Input natural number: " );
              scanf( "%u", &x );
           
              // альтернатива fflush( stdin )
              {
                  char buf[ 64 ];
                  setvbuf( stdin, buf, _IOLBF, sizeof( buf ) );
              }
           
              printf( "Input string: " );
              gets( s );
              
              printf( "Input string: " );
              gets( s );
           
           
              free( s );
              system( "pause" );
              return EXIT_SUCCESS;
          }


        Qraizer, как относишься к коду типа этого:
        ExpandedWrap disabled
              scanf( "%u", &x );
           
              // альтернатива fflush( stdin )
              {
                  char buf[ 64 ];
                  setvbuf( stdin, buf, _IOLBF, sizeof( buf ) );
              }
           
              printf( "Input string: " );


        речь про вложенные скобки {}, в данном случае я так сделал, чтобы избежать ГЛОБАЛЬНОГО по отношению к функции мэйн объявления buf[ 64 ], т к маг.число + не сразу понятно, зачем нужна переменная. А так она скрыта в этой области между { и } и никому не мешает :)
        или это оч.плохой стиль? :unsure:
          Цитата FasterHarder @
          1. В студии 2010 можно спокойно использовать fflush( stdin ) и НИЧЕГО не бояться?
          Я понятия не имею, когда поведение fflush() изменилось. Наверное, или в VS 2015, т.к. это первая студия, начавшая потихонечку поддерживать C++17, или VS 2017, которая в каком-то очередном апдейте поддерживает его в общем-то полностью. Но не факт, я просто не думаю, что они сделали бы посреди жизненного цикла продукта, логично ожидать подобного на стыке мажорных версий.
          Цитата FasterHarder @
          2. Почему для буфера берешь длину 64, ...
          Та просто степень двойки. Вообще, пофигу, какой там буфер, важно, чтоб сменился. Для файловых операций имеет смысл делать его кратным размеру кластера, для консольных... та фикъегознает, можно и вообще буферизацию выключить. Наверно.
          Цитата FasterHarder @
          Qraizer, как относишься к коду типа этого:
          ...
          речь про вложенные скобки {}, в данном случае я так сделал, чтобы избежать ГЛОБАЛЬНОГО по отношению к функции мэйн объявления buf[ 64 ], т к маг.число + не сразу понятно, зачем нужна переменная. А так она скрыта в этой области между { и } и никому не мешает
          или это оч.плохой стиль?
          Вложенные блоки – офигенная вещь, особенно в Плюсах, где царствует RAII. Но и в C место найдётся, т.к. возможность размещать определения посреди блоков возможно лишь с C99. Если тебе нужна переменная с инициализацией, значение которой рассчитывается по ходу дела, то включить её в некий внутренний блок, чтобы не нарушить правило C89/90 размещать определения до любого исполняемого кода в блоке, вполне разумное решение.
          Но не тут. Ты ж устанавливаешь внешний буфер для stdin, а по выходу из области видимости блока буфер существовать перестаёт. Тогда уж лучше просто запрети буферизацию _IONBF вместо _IOLBF
            Цитата Qraizer @
            Но не тут. Ты ж устанавливаешь внешний буфер для stdin, а по выходу из области видимости блока буфер существовать перестаёт.

            вроде я понял, т е этим блоком временно создаем этот буфер buf, при выходе из него снова включается стандартный буфер для потока stdin.
            Но с др.стороны не оч.понятно, почему стандартный буфер не работает так, как надо(иногда приводит к проскакиванию), а этот локальный buf вполне хороший.

            Цитата Qraizer @
            Тогда уж лучше просто запрети буферизацию _IONBF вместо _IOLBF

            это все интересно + интересно, что случится с функцией getchar(), которая буферизует данные до нажатия ENTER...

            Цитата Qraizer @
            Если тебе нужна переменная с инициализацией, значение которой рассчитывается по ходу дела, то включить её в некий внутренний блок, чтобы не нарушить правило C89/90 размещать определения до любого исполняемого кода в блоке, вполне разумное решение.

            например, есть одномерный массив целых чисел, всякая обработка над ним происходит и на каком-то этапе нужно вывести на экран, например, сумму его элементов, то делаем так:
            ExpandedWrap disabled
              {
                 size_t i;
                 long sum = 0;
                  
                 for( i = 0; i < n; i++ )
                     sum += v[ i ];
                 printf( "\nSum elements is: %ld", sum );
              }
               
              // и здесь код пошел дальше


            это норм. пример или неудачный? Если неудачный, то приведи плз что-нибудь короткое, поясняющее суть всего этого...
              Норм. Но я не совсем это имело в виду. Ты тут просто залокалил в блоке пару переменных, почему бы и нет. Но вот сюда глянь:
              ExpandedWrap disabled
                int someFunc(void)
                {
                #ifdef DEBUG
                #define DEBUG_OUT                                                             \
                  {                                                                           \
                    char buf[128];                                                            \
                                                                                              \
                    sprintf(buf, "someVar is %d, someString is \"%s\"", someVar, someString); \
                    debugOut(buf);                                                            \
                  }
                #else
                #define DEBUG_OUT
                #endif
                 
                  int someVar = 0;
                  char *someString = malloc(64);
                 
                  if (someStrting == NULL) return 0;
                  /* тут что-то делается */
                  DEBUG_OUT
                  /* тут делается что-то ещё */
                  DEBUG_OUT
                  /* тут делается совсем что-то другое */
                  free(someString);
                  return someVar;
                #undef DEBUG_OUT
                }
              Тут без {} возможна ситуация, что buf[] пересечётся с каким-нибудь другим buf, в кроме того, макрос DEBUG_OUT просто не смог бы так просто ввести локальную для себя buf. В Плюсах я бы привёл менее синтетический пример, но даже в C локальные блоки бывают полезны не только ради олокаливания имён.
              Цитата FasterHarder @
              Но с др.стороны не оч.понятно, почему стандартный буфер не работает так, как надо(иногда приводит к проскакиванию), а этот локальный buf вполне хороший.
              Так дело не в буфере, а в факте его изменения. stdio вынужден сбросить имеющийся буфер, чтобы принять в работу новый. На самом деле это недокументировано, и даже более того, сказано, что setvbuf() должна вызываться только до первых операций ввода/вывода. Собственно поэтому я и решил, что будет достигнут желаемый эффект. По крайней мере для потока ввода. Всё ж разумней вручную его вычищать, каким-нибудь while (getchar() != '\n') ;
              Цитата FasterHarder @
              интересно, что случится с функцией getchar(), которая буферизует данные до нажатия ENTER...
              Сама по себе getchar() ничего не буферизирует. Буферизирует подсистема ввода/вывода, с которой stdio-функции, getchar() в частности, взаимодействуют. Это как дисковый кэш: на уровне интерфейса его как бы нет, но он-таки есть, прозрачно работает и успешно справляется с возложенными обязанностями. getchar() обращается к этому кешу, мол, дай буковку, и тот даёт, если она в буфере есть; а если нет, то читает в буфер, сколько читается, и возвращается к предыдущему шагу: отдаёт буковку getchar().
                После таких ответов Qraizer-а понимаю, что практически не знаю С, не выкупаю кучу нюансов )

                Вот если предельно все упростить и, например, добавить такую функцию:
                ExpandedWrap disabled
                  // гарантированная очистка буфера stdin для любых стандартов С
                  void my_fflush( void )
                  {
                      fflush( NULL );     // сбрасываем буферы всех открытых потоков данных
                      while( getchar() != '\n' )
                          ;
                  }


                и дергать ее после каждого ввода с клавиатуры (может, после ввода строк функцией gets() дергать не нужно, а вот после ввода числовых данных нужно 100%), ведь тогда проблем с проскакиванием не будет в любом стандарте и в любой версии Студии?

                Например:
                ExpandedWrap disabled
                      size_t x;
                      char* s = ( char* )malloc( 100 );
                   
                      printf( "Input string: " );
                      gets( s );
                   
                      printf( "Input natural: " );
                      scanf( "%u", &x );
                      my_fflush();
                   
                      printf( "Input string: " );
                      gets( s );


                все прекрасно, раньше проскакивал ввод последней строки, сейчас все замечательно...
                или не все так замечательно, как кажется все-таки
                  Думаю, fflush(NULL) можно без опаски удалить. На потоки ввода всё равно не повиляет, а потоки вывода вне обсуждаемого контекста.
                    Цитата Qraizer @
                    Думаю, fflush(NULL) можно без опаски удалить. На потоки ввода всё равно не повиляет, а потоки вывода вне обсуждаемого контекста.

                    понял, спс.

                    Подытожим, чтобы раз и навсегда закрыть эту проблему (одну из тысяч):
                    Независимо от компилятора, версии Visual Studio, ОС (винда, *.никс и др.), если есть ввод с клавиатуры, то надо создать такую функцию:

                    ExpandedWrap disabled
                      void my_fflush( void )
                      {
                          while( getchar() != '\n' )
                              ;
                      }


                    и втыкать ее ПОСЛЕ КАЖДОГО ввода (не важно что вводится: строка, дробное, целое, символ или что-то еще) и тогда не будет ситуаций, когда написал прожку, а потом тебе звонят или пишут письмо, мол "твоя программа не работает, dude", т к ввод проскакивает в каком-то месте. :yes:
                    -------------------------
                    Но есть еще файловая обработка (считывание, например)...Хм... Если честно, то там таких проблем не припомню, там последняя строка бывает дублируется при считывании, но там вроде не нужно буфера сбрасывать...или все-таки...

                    зы: во всех программа больше не юзать fflush( stdin ) ---> my_fflush и будет все ок) Но, наверняка найдется ситуация, когда my_fflush() не спасет все равно :unsure:
                      Qraizer, нашел описание про fflush (правда это про ЛИНУКС все, жаль, что упоминание языка Си в основном связано не с ВИНДОВС...) и вроде бы в своем ПРЕДпоследнем посте ты об этом мне писал как раз:

                      Скрытый текст
                      При вызове этой функции все незаписанные данные из потока данных, указанного в stream, сбрасываются в буфер ядра. Если значение stream равно NULL, то все
                      открытые потоки данных этого процесса записываются в буфер ядра. В случае успеха fflush() возвращает 0. В случае ошибки эта функция возвращает EOF и при
                      сваивает errno соответствующее значение.Чтобы понять принцип действия fflush(), следует понимать разницу между буфером, поддерживаемым библиотекой C, и буферизацией, выполняемой в самом ядре. Все вызовы, описанные в данной главе, работают с буфером, поддерживаемым библиотекой C. Этот буфер располагается в пользовательском пространстве, и, следовательно, в нем работает пользовательский код, а не выполняются системные вызовы. Системный вызов выдается, только когда необходимо обратиться к диску или какому­ нибудь другому носителю.

                      Функция fflush() просто записывает данные из пользовательского буфера в буфер ядра. Получается эффект, как будто пользовательская буферизация вообще не задействовалась и мы напрямую применили вызов write(). В данной ситуации не гарантируется физическая отправка данных на тот или иной носитель — для полной уверенности в благополучной отправке данных потребуется использовать что­то вроде fsync(). В ситуациях, когда необходимо знать, что ваши данные успешно отправлены в резервное хранилище, целесообразно вызвать fsync() непосредственно после fflush(). Таким образом, сначала мы убеждаемся, что информация из пользовательского буфера перенесена в буфер ядра, а потом гарантируем, что информация из буфера ядра попадет на диск


                      зы: буферизация в самом ядре - красиво звучит)
                        Вот, возможно, еще 1 из способов, как можно побеждать проскакивание при вводе, без обращения к fflush( my_fflush ).
                        На этом коде идет проскакивание ( VS 2010 ):

                        ExpandedWrap disabled
                              char s[ BUFSIZ ];
                              int n;
                           
                              printf( "Input number: " );
                              scanf( "%d", &n );
                           
                              printf( "Input string: " );
                              scanf( "%[^\n]%", s );
                           
                              printf( "\n\nS = %s; n = %d\n\n", s, n );


                        после ввода числа невозможно ввести строку в переменную s.

                        Делаю так:
                        ExpandedWrap disabled
                              printf( "Input number: " );
                              scanf( "%d%*c", &n );    // игнорируем символ %*c
                           
                              printf( "Input string: " );
                              scanf( "%[^\n]", s );
                           
                              printf( "\n\nS = %s; n = %d", s, n );

                        проскакивание нет, все ОТЛИЧНО работает.

                        Но, ИМХО, самый правильный вариант, также добавить %*c при считывании строки, т к потом также может пойти проскакивание:
                        ExpandedWrap disabled
                              printf( "Input number: " );
                              scanf( "%d%*c", &n );
                           
                              printf( "Input string: " );
                              scanf( "%[^\n]%*c", s );


                        В студиях MS это работает отлично.

                        Qraizer, а вот как дела обстоят под компиляторы не от MS?? %*c везде спасет?) Подскажи, плз, если в курсе...
                          FasterHarder, понимаешь, написать идеальный код невозможно. Что если пользователь, заранее зная, что ему там дальше придётся вводить ещё несколько полей, сразу всё и введёт? Ты этот ввод просто выкидываешь, считая, что там ничего, кроме \n быть не может. Это не конкретно по последнему посту, это вообще по теме темы. Пытаться что-либо написать, ориентируясь на всевозможные пользовательские фетиши бесполезно.
                          По последнему посту могу сказать, что по факту ты ничего не улучшил, просто меньше букв в программе понадобилось. После числа игнорить тогда уж нужно не один символ, а все до \n. Но если принять к сведению предыдущий абзац, то всё с точностью до наоборот: ввести один символ, проверить его на \n, и ежели не он, затолкнуть обратно ungetc().
                          Но повторюсь, что избыточная забота над нуждами пользователей до добра не доведёт.

                          Добавлено
                          P.S. Само по себе наличие * после % описывается Стандартом. Так что можешь пользовать без ограничений.
                            ок, понял, ладно)
                            0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                            0 пользователей:


                            Рейтинг@Mail.ru
                            [ Script execution time: 0,0938 ]   [ 17 queries used ]   [ Generated: 29.03.24, 01:06 GMT ]