Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.207.133.13] |
|
Сообщ.
#1
,
|
|
|
Всем хай! Сходу к делу!
Есть код, который запрашивает ввод данных с клавиатуры. Известная проблема, что при вводе строковых данных начинает ввод проскакивать (пропускать). Спасает обычно fflush( stdin ), который очищает буффер потока. Недавно была такая проблема. Есть такой код (псевдо): ввод целое вводе строка ввод дробное ввод строка ввод строка чтобы не париться, поставил после каждого ввода fflush( stdin ); ввод целое fflush( stdin ); ввод строка fflush( stdin ); ... все отлично работает. Этот код в Visual Studio 2010 запускается. Запускается под Visual Studio 2019 - ввод проскакивает!! fflush( stdin ) - НЕ спасает. Причем только в одном месте, между вводом двух строковых данных. Т е свою функцию fflush( stdin ) перестал выполнять. Какой стандарт в студии 2019 года - я не знаю, наверное, на С89, а постарше. В итоге пришлось победить эту проблему тупо вставив getchar(); и все стало ок. Вопрос: как побеждать правильно проскакивание ввода данных в студии 2019 года?? спс. за внимание Добавлено Речь про код языка "чистый" Си (никакого С++, ни микрона, хотя в С++ что-то подобное было и там локально как-то побеждалось cin.get()) |
Сообщ.
#2
,
|
|
|
Насколько я изучил ситуацию, эта разница в поведении неустранима. Стандарт вообще не определяет поведение fflush() для входных потоков, только для выходных, для которых выполняется сброс кешированных данных в поток. Что до MS, то насколько я могу судить, она попыталась реализовать поведение fflush(), совместимое с POSIX. Однако если погуглить, многие специалисты даже в POSIX не рекомендуют использовать fflush() для потоков ввода. Причина в том, что это зачастую приводит к непредсказуемым результатам из-за неконтролируемой программным методом величины отбрасываемого ввода. Который в свою очередь зависит от внутренних алгоритмов буферизации стандартных функций ввода/вывода и возможно даже механизмов буферизации ОС.
Можно сымитировать fflush() посредством setvbuf(). Попробуй setvbuf(stdin, buf, _IOLBF, sizeof(buf)) при каком-нибудь char buf[64]. По идее установка своего буфера также должна сбрасывать любую имеющуюся буферизацию. |
Сообщ.
#3
,
|
|
|
Qraizer, круто!
еще ряд уточнений: 1. В студии 2010 можно спокойно использовать fflush( stdin ) и НИЧЕГО не бояться? 2. Почему для буфера берешь длину 64, а не 63, например, или 38? 64 = 8 * 8 или 8байт, может с этим как-то связано или что-то типа около того... В итоге сделал, как ты написал (правда тестил в 2010) и ничего не проскакивает. Вот на этом коде тестировал (проскакивало после ввода числа): #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, как относишься к коду типа этого: scanf( "%u", &x ); // альтернатива fflush( stdin ) { char buf[ 64 ]; setvbuf( stdin, buf, _IOLBF, sizeof( buf ) ); } printf( "Input string: " ); речь про вложенные скобки {}, в данном случае я так сделал, чтобы избежать ГЛОБАЛЬНОГО по отношению к функции мэйн объявления buf[ 64 ], т к маг.число + не сразу понятно, зачем нужна переменная. А так она скрыта в этой области между { и } и никому не мешает или это оч.плохой стиль? |
Сообщ.
#4
,
|
|
|
Цитата FasterHarder @ Я понятия не имею, когда поведение fflush() изменилось. Наверное, или в VS 2015, т.к. это первая студия, начавшая потихонечку поддерживать C++17, или VS 2017, которая в каком-то очередном апдейте поддерживает его в общем-то полностью. Но не факт, я просто не думаю, что они сделали бы посреди жизненного цикла продукта, логично ожидать подобного на стыке мажорных версий.1. В студии 2010 можно спокойно использовать fflush( stdin ) и НИЧЕГО не бояться? Цитата FasterHarder @ Та просто степень двойки. Вообще, пофигу, какой там буфер, важно, чтоб сменился. Для файловых операций имеет смысл делать его кратным размеру кластера, для консольных... та фикъегознает, можно и вообще буферизацию выключить. Наверно.2. Почему для буфера берешь длину 64, ... Цитата FasterHarder @ Вложенные блоки – офигенная вещь, особенно в Плюсах, где царствует RAII. Но и в C место найдётся, т.к. возможность размещать определения посреди блоков возможно лишь с C99. Если тебе нужна переменная с инициализацией, значение которой рассчитывается по ходу дела, то включить её в некий внутренний блок, чтобы не нарушить правило C89/90 размещать определения до любого исполняемого кода в блоке, вполне разумное решение.Qraizer, как относишься к коду типа этого: ... речь про вложенные скобки {}, в данном случае я так сделал, чтобы избежать ГЛОБАЛЬНОГО по отношению к функции мэйн объявления buf[ 64 ], т к маг.число + не сразу понятно, зачем нужна переменная. А так она скрыта в этой области между { и } и никому не мешает или это оч.плохой стиль? Но не тут. Ты ж устанавливаешь внешний буфер для stdin, а по выходу из области видимости блока буфер существовать перестаёт. Тогда уж лучше просто запрети буферизацию _IONBF вместо _IOLBF |
Сообщ.
#5
,
|
|
|
Цитата Qraizer @ Но не тут. Ты ж устанавливаешь внешний буфер для stdin, а по выходу из области видимости блока буфер существовать перестаёт. вроде я понял, т е этим блоком временно создаем этот буфер buf, при выходе из него снова включается стандартный буфер для потока stdin. Но с др.стороны не оч.понятно, почему стандартный буфер не работает так, как надо(иногда приводит к проскакиванию), а этот локальный buf вполне хороший. Цитата Qraizer @ Тогда уж лучше просто запрети буферизацию _IONBF вместо _IOLBF это все интересно + интересно, что случится с функцией getchar(), которая буферизует данные до нажатия ENTER... Цитата Qraizer @ Если тебе нужна переменная с инициализацией, значение которой рассчитывается по ходу дела, то включить её в некий внутренний блок, чтобы не нарушить правило C89/90 размещать определения до любого исполняемого кода в блоке, вполне разумное решение. например, есть одномерный массив целых чисел, всякая обработка над ним происходит и на каком-то этапе нужно вывести на экран, например, сумму его элементов, то делаем так: { size_t i; long sum = 0; for( i = 0; i < n; i++ ) sum += v[ i ]; printf( "\nSum elements is: %ld", sum ); } // и здесь код пошел дальше это норм. пример или неудачный? Если неудачный, то приведи плз что-нибудь короткое, поясняющее суть всего этого... |
Сообщ.
#6
,
|
|
|
Норм. Но я не совсем это имело в виду. Ты тут просто залокалил в блоке пару переменных, почему бы и нет. Но вот сюда глянь:
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 } Цитата FasterHarder @ Так дело не в буфере, а в факте его изменения. stdio вынужден сбросить имеющийся буфер, чтобы принять в работу новый. На самом деле это недокументировано, и даже более того, сказано, что setvbuf() должна вызываться только до первых операций ввода/вывода. Собственно поэтому я и решил, что будет достигнут желаемый эффект. По крайней мере для потока ввода. Всё ж разумней вручную его вычищать, каким-нибудь while (getchar() != '\n') ;Но с др.стороны не оч.понятно, почему стандартный буфер не работает так, как надо(иногда приводит к проскакиванию), а этот локальный buf вполне хороший. Цитата FasterHarder @ Сама по себе getchar() ничего не буферизирует. Буферизирует подсистема ввода/вывода, с которой stdio-функции, getchar() в частности, взаимодействуют. Это как дисковый кэш: на уровне интерфейса его как бы нет, но он-таки есть, прозрачно работает и успешно справляется с возложенными обязанностями. getchar() обращается к этому кешу, мол, дай буковку, и тот даёт, если она в буфере есть; а если нет, то читает в буфер, сколько читается, и возвращается к предыдущему шагу: отдаёт буковку getchar(). интересно, что случится с функцией getchar(), которая буферизует данные до нажатия ENTER... |
Сообщ.
#7
,
|
|
|
После таких ответов Qraizer-а понимаю, что практически не знаю С, не выкупаю кучу нюансов )
Вот если предельно все упростить и, например, добавить такую функцию: // гарантированная очистка буфера stdin для любых стандартов С void my_fflush( void ) { fflush( NULL ); // сбрасываем буферы всех открытых потоков данных while( getchar() != '\n' ) ; } и дергать ее после каждого ввода с клавиатуры (может, после ввода строк функцией gets() дергать не нужно, а вот после ввода числовых данных нужно 100%), ведь тогда проблем с проскакиванием не будет в любом стандарте и в любой версии Студии? Например: 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 ); все прекрасно, раньше проскакивал ввод последней строки, сейчас все замечательно... или не все так замечательно, как кажется все-таки |
Сообщ.
#8
,
|
|
|
Думаю, fflush(NULL) можно без опаски удалить. На потоки ввода всё равно не повиляет, а потоки вывода вне обсуждаемого контекста.
|
Сообщ.
#9
,
|
|
|
Цитата Qraizer @ Думаю, fflush(NULL) можно без опаски удалить. На потоки ввода всё равно не повиляет, а потоки вывода вне обсуждаемого контекста. понял, спс. Подытожим, чтобы раз и навсегда закрыть эту проблему (одну из тысяч): Независимо от компилятора, версии Visual Studio, ОС (винда, *.никс и др.), если есть ввод с клавиатуры, то надо создать такую функцию: void my_fflush( void ) { while( getchar() != '\n' ) ; } и втыкать ее ПОСЛЕ КАЖДОГО ввода (не важно что вводится: строка, дробное, целое, символ или что-то еще) и тогда не будет ситуаций, когда написал прожку, а потом тебе звонят или пишут письмо, мол "твоя программа не работает, dude", т к ввод проскакивает в каком-то месте. ------------------------- Но есть еще файловая обработка (считывание, например)...Хм... Если честно, то там таких проблем не припомню, там последняя строка бывает дублируется при считывании, но там вроде не нужно буфера сбрасывать...или все-таки... зы: во всех программа больше не юзать fflush( stdin ) ---> my_fflush и будет все ок) Но, наверняка найдется ситуация, когда my_fflush() не спасет все равно |
Сообщ.
#10
,
|
|
|
Qraizer, нашел описание про fflush (правда это про ЛИНУКС все, жаль, что упоминание языка Си в основном связано не с ВИНДОВС...) и вроде бы в своем ПРЕДпоследнем посте ты об этом мне писал как раз:
Скрытый текст При вызове этой функции все незаписанные данные из потока данных, указанного в stream, сбрасываются в буфер ядра. Если значение stream равно NULL, то все открытые потоки данных этого процесса записываются в буфер ядра. В случае успеха fflush() возвращает 0. В случае ошибки эта функция возвращает EOF и при сваивает errno соответствующее значение.Чтобы понять принцип действия fflush(), следует понимать разницу между буфером, поддерживаемым библиотекой C, и буферизацией, выполняемой в самом ядре. Все вызовы, описанные в данной главе, работают с буфером, поддерживаемым библиотекой C. Этот буфер располагается в пользовательском пространстве, и, следовательно, в нем работает пользовательский код, а не выполняются системные вызовы. Системный вызов выдается, только когда необходимо обратиться к диску или какому нибудь другому носителю. Функция fflush() просто записывает данные из пользовательского буфера в буфер ядра. Получается эффект, как будто пользовательская буферизация вообще не задействовалась и мы напрямую применили вызов write(). В данной ситуации не гарантируется физическая отправка данных на тот или иной носитель — для полной уверенности в благополучной отправке данных потребуется использовать чтото вроде fsync(). В ситуациях, когда необходимо знать, что ваши данные успешно отправлены в резервное хранилище, целесообразно вызвать fsync() непосредственно после fflush(). Таким образом, сначала мы убеждаемся, что информация из пользовательского буфера перенесена в буфер ядра, а потом гарантируем, что информация из буфера ядра попадет на диск зы: буферизация в самом ядре - красиво звучит) |
Сообщ.
#11
,
|
|
|
Вот, возможно, еще 1 из способов, как можно побеждать проскакивание при вводе, без обращения к fflush( my_fflush ).
На этом коде идет проскакивание ( VS 2010 ): 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. Делаю так: printf( "Input number: " ); scanf( "%d%*c", &n ); // игнорируем символ %*c printf( "Input string: " ); scanf( "%[^\n]", s ); printf( "\n\nS = %s; n = %d", s, n ); проскакивание нет, все ОТЛИЧНО работает. Но, ИМХО, самый правильный вариант, также добавить %*c при считывании строки, т к потом также может пойти проскакивание: printf( "Input number: " ); scanf( "%d%*c", &n ); printf( "Input string: " ); scanf( "%[^\n]%*c", s ); В студиях MS это работает отлично. Qraizer, а вот как дела обстоят под компиляторы не от MS?? %*c везде спасет?) Подскажи, плз, если в курсе... |
Сообщ.
#12
,
|
|
|
FasterHarder, понимаешь, написать идеальный код невозможно. Что если пользователь, заранее зная, что ему там дальше придётся вводить ещё несколько полей, сразу всё и введёт? Ты этот ввод просто выкидываешь, считая, что там ничего, кроме \n быть не может. Это не конкретно по последнему посту, это вообще по теме темы. Пытаться что-либо написать, ориентируясь на всевозможные пользовательские фетиши бесполезно.
По последнему посту могу сказать, что по факту ты ничего не улучшил, просто меньше букв в программе понадобилось. После числа игнорить тогда уж нужно не один символ, а все до \n. Но если принять к сведению предыдущий абзац, то всё с точностью до наоборот: ввести один символ, проверить его на \n, и ежели не он, затолкнуть обратно ungetc(). Но повторюсь, что избыточная забота над нуждами пользователей до добра не доведёт. Добавлено P.S. Само по себе наличие * после % описывается Стандартом. Так что можешь пользовать без ограничений. |
Сообщ.
#13
,
|
|
|
ок, понял, ладно)
|