nixp.ru v3.0

23 мая 2017,
вторник,
11:58:21 MSK

DevOps с компанией «Флант»
Fatal написал 14 июля 2004 года в 22:23 (5621 просмотр) Ведет себя как мужчина; открыл 123 темы в форуме, оставил 484 комментария на сайте.

Как чистить стандартный поток вывода, после неправильного введенного символо с помощью функции scanf? с помощью fflush не получается — это работает только в консоли под Window/Dos. Я пробую писать программы под FreeBSD, а там ничерта не работает fflush или ведет себя по другому. Почему так? Я пробовал также с функцией clearerr(stdin), тоже не выходит.

#include

#include

int main(void)

{

int i=0,t,n;

do{

t=scanf(«%d»,&n);

fflush(stdin);

//clearerr(stdin);

if(t==1)break;

++i;

}while(i<3);

printf(«\ni=%d\n»,i);

return 0;

}

sas

попробуйте

#include

#include

int main(void)

{

   int i=0,t,n;

   do{

     t=scanf(«%d»,&n);

     if ( t <= 0 ) {

     if ( ferror( stdin ) || feof( stdin ) ) clearerr( stdin );

     else rewind( stdin );

   }

   else

     break;

   ++i;

   } while(i<3);

   printf(«\ni=%d\n»,i);

   return 0;

}

Удачи

— sas

PS code was not compiled and so may have errors

Fatal

Так почему же в FreeBSD не работает fflush? Почему он не очищает поток?

sas

Потому что:

1 На сколько я знаю fflush stdin не может гарантированно работать по стадартам

2 Он не очищает аппаратных буферов

3 scanf имеет собственный буфер

Можно и по другому чистить:

scanf( «%*[^\n]» );

что означает пропусти весь инпут до '\n'

Я не знаю что более портабельно — rewind или очистка буфера. Пока я испльзую rewind. На всех «моих» платформах он работает

Кстати scanf семейство я не люблю. Гораздо более гибко читать строку и парзить самому, т.к. например в Вашем коде не будет работать даже строка, где будет более одного целого.

Например:

1 2 3 4 5

Удачи

—  sas

ЗЫ

После моего ответа посмотрел в стандарте о том что лучше использовать rewind или очистку буфера и нашел, что rewind НЕ ГОДИТСЯ для использования! По стандарту он может применятся только к файлам, которые поддерживают позционирование. stdin НЕ ПОДДЕРЖИВАЕТ его!

Правильнее использовать scanf очистку! Кстати предидущее решение НЕ будет работать если входная строка содержит более одного '\n' В этом случае надо вызвать еще один scanf т.е.

if ( 0 == t ) {

  scanf( «%*[^\n]» );

  scanf( «%*c» );    /* уберем оставшиеся '\n'  */

}

Вот теперь я надеюсь все

Fatal

1. fflush — это стаднартная функция ANSI и она обязана работать по стандартам!

2. к сожалению я не понимаю, что такое аппаратный буфер …

3. scanf выводит в стандартный поток вывода — stdin

scanf( «%*[^\n]» ) — эта строка будет пропускать и оставлять их в stdin? Если да, то это не годится, т.к. это будет влиять на другие функции, которые используют stdin. К примеру:

char s[1000], b[1000];

scanf(«%s»,&s);/*ввести слова через пробелы и нажать в конце Enter: ыаыва ываыва ываыв Enter*/

/*сканф затяпает только одно слово до пробела, а остальные оставит в буфере stdin*/

fgets(b,999,stdin);/*эта функция не будет ожидать ввода, он съест все что осталось после сканфа в stdin*/

В man описана еще одна функция (не понятно почему она объявлена в stdio — это ведь не стандартная функция) int fpurge(FILE*). Так вот если ее подставить вместо fflush(stdin), т.е. fpurge(stdin), то все будет чики-пуки, что на местно жаргоне означает хорошо.

Функция fflush возвращает код ошибки 9 (Bad file descriptor), а функция fpurge возвращает 0 (OK). Не понятно почему fflush не понравился дескриптор стандартного вывода?!

И еще я заметил, что fputs(«Hello!\n»,stdin) тоже не фурычит. Т.е. если после этого вызова написать fgets он будет ожидать ввода, хотя по логике он должен взять из буфера stdin. В чем может быть проблема, может надо открыть заново stdin. Но как это сделать? Чертовщина какая-то! Конечно можно fflush заменить на fpurge(File*), но нужна стандартная функция, которая бы обеспечивала переносимость. Конечно вывернуться можно, но все же хочется по-нормальному. Это глюк BSD?!

У кого есть возможность попробовать в Linux кусок кода, который я привел ниже, попробуйте, пожалуйста. И напишите будет ли нормально работать fflush в линуксе.

sas
Fatal
1. fflush — это стаднартная функция ANSI и она обязана работать по стандартам!

Правильно. Но Вам надо внимательнее читать man:

<<

The function fflush() forces a write of all buffered data for the given

    output or update stream via the stream’s underlying write function.  The

    open status of the stream is unaffected.

>>

stdin == input stream — Только чтение из него возможно

2. к сожалению я не понимаю, что такое аппаратный буфер …

Аппаратный буфер клавиатуры — там хранятся scan codes нажатых клавиш до того как они будут прочитаны драйвером клавиатуры

3. scanf выводит в стандартный поток вывода — stdin

scanf читает из stdin

scanf( «%*[^\n]» ) — эта строка будет пропускать и оставлять их в stdin? Если да, то это не годится, т.к. это будет влиять на другие функции, которые используют stdin.

scanf считывает и удаляет из буфера stdin пpочитанные символы. Опять же Вам надо внимательнее читать man. Звездочка после процента означает для scanf следующее: возьми из буфера и забудь

[^\n] — возьми любой символ, только не '\n’, что означает что буфер stdin после этого вызова будет содержать от 1 до  N '\n'

Второй предложенный вызов уберет и эти оставшиеся '\n'

Опять же из-за аппаратного буфера не может быть 100% уверенности что после этих вызовов буфер stdin будет полностью пустым

К примеру:

char s[1000], b[1000];

scanf(«%s»,&s);/*ввести слова через пробелы и нажать в конце Enter: ыаыва ываыва ываыв Enter*/

/*сканф затяпает только одно слово до пробела, а остальные оставит в буфере stdin*/

fgets(b,999,stdin);/*эта функция не будет ожидать ввода, он съест все что осталось после сканфа в stdin*/


2 строчки кода и 2 ошибки включая одну критическую:

1. Советую никогда не использовать числа при объявлении массивов, только константы ( define в С или const int|long  в С++)

#define BUFSZ 1000

2. В первый вызов scanf Вы передаете указатель на указатель. Это и есть критическая ошибка

3. Опять же читайте man: fgets читает максимум size — 1 символ или до появления '\n' если он появится  до size — 1. Это означает что:

  fgets( b, BUFSZ, stdin );

В man описана еще одна функция (не понятно почему она объявлена в stdio — это ведь не стандартная функция) int fpurge(FILE*).  Так вот если ее подставить вместо fflush(stdin), т.е. fpurge(stdin), то все будет чики-пуки, что на местно жаргоне означает хорошо.

fpurge — расширение BSD В Linux например есть __fpurge отличается тем, что не возвращает значения. Как Вы уже верно заметили — это не стандартная функция и использовать ее поэтому не рекомендуется

Чтобы понять почему она работает опять читаем внимательно man:

<<

The function fpurge() erases any input or output buffered in the given

    stream.  For output streams this discards any unwritten output.  For

    input streams this discards any input read from the underlying object but

    not yet obtained via getc(3); this includes any text pushed back via

    ungetc.

>>

те она работает, в отличии от fflush, и на input streams

Функция fflush возвращает код ошибки 9 (Bad file descriptor), а функция fpurge возвращает 0 (OK). Не понятно почему fflush не понравился дескриптор стандартного вывода?!

Cм выше или man fflush работает на output потоках

И еще я заметил, что fputs(«Hello!\n»,stdin) тоже не фурычит. Т.е. если после этого вызова написать fgets он будет ожидать ввода, хотя по логике он должен взять из буфера stdin. В чем может быть проблема, может надо открыть заново stdin. Но как это сделать? Чертовщина какая-то! Конечно можно fflush заменить на fpurge(File*), но нужна стандартная функция, которая бы обеспечивала переносимость. Конечно вывернуться можно, но все же хочется по-нормальному. Это глюк BSD?!

Опять же это не глюк BSD. Вы не можете писать в stdin напрямую. Абсолютно не понятно чего Вы хотите добиться в Вашем приложении.

У кого есть возможность попробовать в Linux кусок кода, который я привел ниже, попробуйте, пожалуйста. И напишите будет ли нормально работать fflush в линуксе.

Исходя из вышеизложенного fflush НЕ ДОЛЖЕН РАБОТАТЬ по Вашему сценарию.

ИТОГ:

1) Будьте внимательнее и читайте man

2) Старайтесь эксперементировать и объяснять сами

3) Иногда лучше задать вопрос не о конкретной ошибке, которая у Вас есть, а о Вашем замысле в целом. Это может помочь выявить принципиальные ошибки в замысле/алгоритме/подходе к проблеме

Успехов

— sas

Fatal

Спасибо большое за такой расклад!

Про man — я не очень хорошо понимаю английский.

Ну так тогда почему fflush чистит поток stdin в Windows (Borland С++, Visual С++).

Я конкретной программы не разробатываю, я просто учюсь, и хочу понять почему стандартная функция fflush работает по разному на разных платформах. А преподы знать не знают про Unix

sas
Fatal
Спасибо большое за такой расклад!

Про man — я не очень хорошо понимаю английский.

Ну так тогда почему fflush чистит поток stdin в Windows (Borland С++, Visual С++).

Я конкретной программы не разробатываю, я просто учюсь, и хочу понять почему стандартная функция fflush работает по разному на разных платформах. А преподы знать не знают про Unix

Ну технический английский Вам просто необходимо подтягивать если Вы собираетесь заниматься программированием. Не так страшен черт как его малюют, хотя в начале будет и тяжело. Покупайте словари и вперед.

По стандарту ANSI С и 1999 года fflush на input потоках не определен.

MS же в своем helpe четко говорит что для инпут потоков fflush чистит буфер. Кроме того MS уверяет что их реализация поддерживает ANSI C :)

MS в своем репертуаре.

Удачи

— sas

PS Купите так называемую белую книгу Кернигана и Ричи «The C Programming Language (ANSI C)» и пусть она станет Вашим проводником :) Ничего лучше по С я не читал.

Fatal

Вы бы не могли, если вы знаете, какой-нибудь адрес в инете где описывается стандарт ANSI

sas

Я советую изучить  Kernigan & Ritchi (см выше) сначала. Он 100% должен быть в магазинах на русском.

http://www.eskimo.com/~scs/C-faq/q11.2.html

Annotated ANSI C Вы можете купить на amazon.com за 27$ (не новый)

Драфт С стандарта можно скачать с

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n869/

Имейте в виду, что окончательный вариант может несколько (не значительно) отличаться от драфта

Удачи

— sas

Fatal

>Опять же из-за аппаратного буфера не может быть 100% уверенности что после этих вызовов буфер stdin будет полностью пустым

sas, Вы не могли бы объяснить почему из-за аппаратного буфера не может быть 100% уеверенности и четакое Драфт.

Спасибо.

Fatal

Вы говорили, что лучше спрашивать про алгоритм. Так вот мне нужно спросить у пользоватьеля выбор из меню, где номер пункта вводится с клавиатуры, принимается ввод scanf. Далее мне нужно запросить у пользователя строку — имя родственика (так требует HomeWork). А fgets проскакивает из-за того-что буфер stdin не пуст.

Можно конечно вызавать fgets 2 раза (первый раз для очистки потока, второй для запроса) или в функции menu, которя приводится ниже, но в этом есть недостатки. Надо дополнительно выделять память. В моем случае выделять память нужно (т.к. массив уже существует для ввода строки и можно использовать его). Но хотелось бы функцию menu сделать самостоятельной, чтобы она самостоятельно очищала поток, и в тоже время не хочется заводить массив внутри этой функции (тем более неизвестно какой длины он понадобится). Ваш метод со scanf не помогает.

Подскажите, пожалуйста как в этой ситуации быть. Как чистить поток после scanf? Хотелось бы это провернуть со стандартными функциями.

#include

#include

int menu(char*str, …);

int main(void){

char s[N];

int test;

test=menu(«Menu»,«One»,«Two»,«Three»);

fgets(s,N,stdin);

return 0;

}

int menu(char*str, …){

int n,i,test,try=-5;

char*s;

va_list p;

clearscr();

do{

i=0;

va_start(p,str);

puts(str);

puts(«0. Exit»);

while(NULL!=(s=va_arg(p,char*)))

printf(«%d. %s\n»,++i,s);

printf(«Enter number 0-%d »,i);

test=scanf(«%d»,&n);

scanf(«%*[^\n]»);

scanf(«%\n»);

clearscr();

if(test==1&&n>=0&&n<=i)break;

puts(«You’re wrong! Try it again, please …»);

}while(++try);

va_end(p);

clearscr();

if(!try)return -1;

else return n;

}

sas

Сценарий:

1) Символы появились в «нижележащих» буферах и еще не были перемещены в буфер stdin

2) Мы прозвели очистку stdin буфера

3) До нашего чтения из stdin буфера символы были положены в stdin буфер

Драфт — это промежуточный вариант, предназначенный для обсуждения.

Посмотрите на код ниже. Он более модульный чем Ваш. Если понадобятся объяснения почему что-то сделано именно так, а не иначе, то задавайте вопросы.

WARNING: Код не тестирован и может содержать ошибки

#include 
#include 
#include 
#define TRIES       5
#define ROWS_SCROLL 50  /* depends on the terminal */
#define NAME_LENGTH 50
void clear_scr( void )
{
  int i;
  /*
    slow and not elegant, but the only one portable way to
    clear screen
  */
  for ( i = 0; i < ROWS_SCROLL; i++ ) printf( "\n" );
}
/**
 * assume that last item should be == NULL
 *
 * Returns: number of items
 */
int draw_menu( char **items )
{
  int i = 1;
  while ( *items )
    printf( "%d) %s\n", i++, *items++ );
  printf( "\nPlease choose your item (1-%d): ", i - 1 );
  return i;
}
/**
 * returns number of the choosed item
 * if input was correct and 0 otherwise
 */
int process_menu_input( int min_item, int max_item )
{
  int item_num = 0;
  if ( scanf( "%d", &item_num ) <= 0 ) {
    if ( feof( stdin ) || ferror( stdin ) ) clearerr( stdin );
  }
  if ( item_num < min_item || item_num > max_item ) item_num = 0;
  /*
      always try to remove everything we know about
      from stdin's buffer
  */
  scanf( "%*[^\n]" );
  scanf( "%*c" );
  return item_num;
}
/*
 * returns name of the user relative.
 */
char *ask_relatives_name( char *name, int name_sz )
{
  int l;
  char *p_name;
  printf( "\nPlease enter your name: " );
  p_name = fgets( name, name_sz, stdin );
  if ( !p_name ) {
    if ( ferror( stdin ) ) perror( "ask_relatives_name" );
    else fprintf( stderr, "ask_relatives_name() EOF" );
    *name = '\0';
  }
  else {
    if ( '\n' == name[ (l = strlen( name ) - 1) ] )
      name[ l ] = '\0';
  }
  return name;
}
int main( int argc, char **argv )
{
  char *items[] = { "One", "Two", "Three", "Four", NULL };
  int i, items_num;
  char name[ NAME_LENGTH ], *p_name;
  for ( i = 0; i < TRIES; i++ ) {
    clear_scr();
    printf(
      "Maximum number of tries is %d; Current try is %d\n",
      TRIES, i + 1
    );
    items_num = draw_menu( items );
    if ( process_menu_input( 1, items_num ) ) {
      p_name = ask_relatives_name( name, sizeof( name ) );
      printf( "\n\nRelative's name is: [%s]\n", p_name );
      break;
    }
  }
  return EXIT_SUCCESS;
}

Удачи

— sas

Fatal

Красиво у вас все расписано, сразу бросается в глаза ваш опыт.

Объясните, пожалуйтса:

1.Зачем stdin проверять на конец файла?

2. Хорошо ли так дробить программу на модули? Скорость работы уменьшается.

3. У меня функция menu была написано с переменным числом параметров для более широкого применения, можно так оставить или лучше сделать как у вас?

Ваша ошибка:

У вас в функции ask_relatives_name проверяется указатель p_name на ноль . Для переносимости это не верно, так делать нельзя. Потому, что указатель проверятеся с нулем типа int. Но не навсех машинах sizeof(int)==sizeof(char*). Поэтому указатель следует проверять NULL!=p_name.

Я проверил вашу программу — олично работатет! It’s great! Спасибо!

Fatal

Я еще забыл спросить, зачем писать:

scanf(«%*[^\n]»);

scanf(«%*c»);

Когда достаточно одной строки:

scanf(«%*c»);

sas
Fatal
Красиво у вас все расписано, сразу бросается в глаза ваш опыт.

Спасибо :O)

Объясните, пожалуйтса:

1.Зачем stdin проверять на конец файла?

stdin, stdout, stderr могут быть подключены к различным типам оборудования. Для каждого типа оборудования EOF может возникнуть по разным причинам. Например в DOS если нажать Ctrl+Z во время выполнения любой консольной программы, то консоль так и будет в EOF пока какая либо другая программа не напишет что-нибудь в stdin/stderr. Это означает, что все программы сразу же читающие с stdin, и запущенные после этого, ничего прочитать с него не смогут пока консоль не будет выведена из этого состояния.

2. Хорошо ли так дробить программу на модули? Скорость работы уменьшается.

1. Это не модули, а функции

2. Да вызов функции медленнее :) , НО

3. Консоль — само по себе устройство медленное. Что мы достигнем?

4. Такой код легче

а) Писать

б) Понимать и сопровождать

в) Использовать повторно

я) И поверьте еще много аргументов «ЗА»

Опять же, читайте белую книгу, «Искусство программирования» и т.д.

3. У меня функция menu была написано с переменным числом параметров для более широкого применения, можно так оставить или лучше сделать как у вас?

Можно, но зачем?

В функции с переменным числом параметров Вам необходимо явно передавать все Ваши пункты меню. Это очень не удобно, не читабельно.

Представьте себе что Вы написали программу, и после пары недель работы у Вас меню меняется. Вам надо найти то место где ВЫЗЫВАЕТСЯ Ваша функция (а вызываться она может в конце или середине большого файла) и менять там вызов. Обычно такого рода данные лучше «хранить» или описывать в самом начале, чтобы сразу видно было.

Или Вам теперь надо хранить меню во внешнем текстовом файле, чтобы его было возможно изменять без перекомпиляции. С массивом эти проблемы решать легче, хотя и тут существуют ограничения.

Ваша ошибка:

У вас в функции ask_relatives_name проверяется указатель p_name на ноль . Для переносимости это не верно, так делать нельзя. Потому, что указатель проверятеся с нулем типа int. Но не навсех машинах sizeof(int)==sizeof(char*). Поэтому указатель следует проверять NULL!=p_name.

:O)

Нет, это НЕ ОШИБКА. Это вид записи, ведь сравнение здесь с NULL указателем. Некоторые говорят, что эта форма записи плохо читается , а некоторые наоборот. Я использую и то и то по настроению.

А теперь объяснение:

ПО СТАНДАРТУ С NULL pointer:

<<

guaranteed to compare unequal to a pointer to any object or function

>>

Как это происходит:

*) fgets в случае ошибки возвращает NULL указатель.

*) В процессе компиляции компилятор развернет if ( !ptr ) в if ( ptr == 0 )

*) Кроме того компилятор знает, что он работает с указателем в этом контексте и поэтому он неявно приведет и ptr и 0 к NULL указателям.

т.е. главное здесь контекст. Кстати если контекст известен, то NULL — это только для повышения читабельности кода. И еще одно кстати: в С++ NULL рекомендуется не использовать (просто 0).

100% правда :) и размеры данных на которые указываем тут значения не имеют.

Kernigan & Ritchie «The C programming language» Second Edition A7.4.7 p. 204

ANSI Secs. 3.3.3.3; 3.3.9; 3.3.13; 3.3.14; 3.3.15; 3.6.5

ISO Secs. 6.3.3.3; 6.3.9; 6.3.13; 6.3.14; 6.3.15; 6.6.5

Более того, все это даже не зависит от того, как представляется 0 на конкретной машине.

Успехов

— sas

sas
Fatal
Я еще забыл спросить,  зачем писать:

scanf(«%*[^\n]»);

scanf(«%*c»);

Когда достаточно одной строки:

scanf(«%*c»);

Потестируйте с нечисловым (ошибочным) вводом и сами ответьте на свой вопрос. Вы уже знаете достаточно о том как работает scanf

Удачи

— sas

Fatal

Я тестировал и именно поэтому я спросил. Если делать с циклом запроса, то разницы нет, если пользователь в конце концов ввел правильно. Но если число попыток ограничено и пользователь ввел не првильно — тогда сбой fgets’а. Но почему так scanf(«%*c»); — читает любые символы, зачем добавлять перед ним scanf(«%*[^\n]»); На практике видно, что неработает без scanf(«%*[^\n]»);, но в теории это непонятно.

sas

Определение:

«ПРОБЕЛЬНЫЕ» символы — ' '; '\t’; '\n' или иначе говоря разделители

Сценарий 1 только scanf( «%*c» );

============================

Мы ввели » \tqwerty» тогда в stdin буфере у нас » \tqwerty\n»

1) scanf( «%d», &n ); Сначала мы читаем и убираем из буфера все разделители. Буфер стал: «qwerty\n». Теперь scanf видит, что 'q' не цифра и он буфер больше не меняет и выходит

2) Вызывается scanf( «%*c» ) (его смысл: убрать 1 символ. Разделитель не считается символом). Он убирает из буфера 'q' и выходит. У нас осталось в stdin буфере «werty\n»

3) Так как мы находимся в цикле, то опять scanf( «%d», &n ); Он видит, что 'w' не цифра и он буфер больше не меняет и выходит

4) опять scanf( «%*c» ) После него осталось «erty\n»

и тд

==========================================

СЦЕНАРИЙ 2 со scanf( «%*[^\n]» ) и scanf( «%*c» )

Мы ввели » \tqwerty» тогда в stdin буфере у нас » \tqwerty\n»

1) scanf( «%d», &n ); Сначала мы читаем и убираем из буфера все разделители. Буфер стал: «qwerty\n». Теперь scanf видит, что 'q' не цифра и он буфер больше не меняет и выходит

2) Вызывается scanf( «%*[^\n]» ) (его смысл: убрать (пропустить) ВСЕ кроме '\n’. ). Он убирает из буфера 'qwerty' и выходит. У нас осталось в stdin буфере «\n»

3) Вызывается scanf( «%*c» ). Как мы помним он хочет найти первый же символ, не являющийся разделителем и забрать его из буфера. '\n' (а их может быть больше чем 1 вообще говоря) — разделитель, поэтому scanf его/их удаляет из буфера пока тот пустым не окажется или какой либо символ не появится. Вот и все наш буфер «опустел».

Удачи

— sas

Fatal

Большое спасибо! Разобрался.