Использование fflush(stdin)

Так что быстрый поиск Google для fflush(stdin) для очистки входного буфера выявляются многочисленные сайты, предупреждающие против его использования. И все же именно так преподаватель CS преподавал классу это делать.

Насколько плохо используется fflush(stdin)? Должен ли я действительно воздерживаться от его использования, даже если мой профессор использует его, и, похоже, он работает безупречно?

8 ответов

Решение

Просто: это неопределенное поведение, так как fflush предназначен для вызова в выходном потоке. Это выдержка из стандарта C:

int fflush (FILE * ostream);

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

Так что вопрос не в том, насколько это плохо. fflush(stdin) это явно неправильно, и вы не должны его использовать, никогда.

Преобразование комментариев в ответ - и расширение их, поскольку проблема периодически появляется.

Стандартный C и POSIX оставить fflush(stdin) как неопределенное поведение

Стандарты POSIX, C и C++ для fflush() явно заявите, что поведение не определено, но ни одно из них не мешает системе определить его.

ISO / IEC 9899: 2011 - стандарт C11 - гласит:

§7.21.5.2 Функция fflush

If2 Если stream указывает на выходной поток или поток обновления, в который не была введена самая последняя операция, fflush функция заставляет любые неписанные данные для этого потока быть доставленными в хост-среду для записи в файл; в противном случае поведение не определено.

POSIX в основном относится к стандарту C, но помечает этот текст как расширение C.

[CX] Для потока, открытого для чтения, если файл еще не находится в EOF, и файл доступен для поиска, смещение файла базового описания открытого файла должно быть установлено в файловую позицию потока, и любой персонажи выталкиваются обратно в поток ungetc() или же ungetwc() которые впоследствии не были прочитаны из потока, должны быть отброшены (без дальнейшего изменения смещения файла).

Обратите внимание, что терминалы не способны искать; ни трубы, ни розетки.

Microsoft определяет поведение fflush(stdin)

Microsoft и среда выполнения Visual Studio определяют поведение fflush() на входном потоке.

Если поток открыт для ввода, fflush очищает содержимое буфера

M.M отмечает:

Cygwin является примером довольно распространенной платформы, на которой fflush(stdin) не очищает ввод.

Вот почему эта ответная версия моего комментария отмечает "среда выполнения Microsoft и Visual Studio" - если вы используете библиотеку времени выполнения не Microsoft C, то поведение, которое вы видите, зависит от этой библиотеки.

Документация и практика Linux кажутся противоречащими друг другу

Удивительно, но Linux номинально документирует поведение fflush(stdin) тоже, и даже определяет это так же (чудо из чудес).

Для входных потоков, fflush() отбрасывает любые буферизованные данные, которые были извлечены из базового файла, но не были использованы приложением.

Я остаюсь немного озадаченным и удивленным документацией Linux, говорящей, что fflush(stdin) буду работать. Несмотря на это предположение, он чаще всего не работает в Linux. Я только что проверил документацию по Ubuntu 14.04 LTS; он говорит то, что цитируется выше, но эмпирически, он не работает - по крайней мере, когда входной поток является устройством без поиска, таким как терминал.

demo-fflush.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c; enter some new data\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

Пример вывода

$ ./demo-fflush
Alliteration
Got A; enter some new data
Got l
$

Этот вывод был получен как на Ubuntu 14.04 LTS, так и на Mac OS X 10.11.2. Насколько я понимаю, это противоречит тому, что говорится в руководстве по Linux. Если fflush(stdin) операция сработала, мне пришлось бы набирать новую строку текста, чтобы получить информацию для второго getchar() читать.

Учитывая то, что говорит стандарт POSIX, возможно, нужна лучшая демонстрация, и документация Linux должна быть разъяснена.

demo-fflush2.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c\n", c);
        ungetc('B', stdin);
        ungetc('Z', stdin);
        if ((c = getchar()) == EOF)
        {
            fprintf(stderr, "Huh?!\n");
            return 1;
        }
        printf("Got %c after ungetc()\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

Пример вывода

Обратите внимание, что /etc/passwd это доступный для поиска файл В Ubuntu первая строка выглядит так:

root:x:0:0:root:/root:/bin/bash

В Mac OS X первые 4 строки выглядят так:

##
# User Database
# 
# Note that this file is consulted directly only when the system is running

Другими словами, в верхней части Mac OS X есть комментарий /etc/passwd файл. Строки без комментариев соответствуют нормальному макету, поэтому root запись:

root:*:0:0:System Administrator:/var/root:/bin/sh

Ubuntu 14.04 LTS:

$ ./demo-fflush2 < /etc/passwd
Got r
Got Z after ungetc()
Got o
$ ./demo-fflush2
Allotrope
Got A
Got Z after ungetc()
Got B
$

Mac OS X 10.11.2:

$ ./demo-fflush2 < /etc/passwd
Got #
Got Z after ungetc()
Got B
$

Поведение Mac OS X игнорирует (или, по крайней мере, кажется, игнорирует) fflush(stdin) (таким образом, не следуя POSIX по этому вопросу). Поведение Linux соответствует документированному поведению POSIX, но спецификация POSIX гораздо более осторожна в том, что в нем говорится - в нем указан файл, который можно искать, но терминалы, конечно, не поддерживают поиск. Это также намного менее полезно, чем спецификация Microsoft.

Резюме

Microsoft документирует поведение fflush(stdin), По-видимому, он работает так, как описано на платформе Windows, с использованием собственного компилятора Windows и библиотек поддержки времени выполнения C.

Несмотря на документацию об обратном, он не работает в Linux, когда стандартным вводом является терминал, но, похоже, он следует спецификации POSIX, которая сформулирована гораздо более тщательно. Согласно стандарту C, поведение fflush(stdin) не определено POSIX добавляет квалификатор "если входной файл не является доступным для поиска", а терминал - нет. Поведение не такое, как у Microsoft.

Следовательно, переносимый код не использует fflush(stdin), Код, привязанный к платформе Microsoft, может использовать его, и он будет работать, но остерегайтесь проблем с переносимостью.

POSIX способ отбросить непрочитанные данные терминала из файлового дескриптора

Стандартный способ POSIX для удаления непрочитанной информации из файлового дескриптора терминала (в отличие от файлового потока, такого как stdin) показано в разделе Как удалить непрочитанные данные из очереди ввода tty в системе Unix. Однако это работает ниже стандартного уровня библиотеки ввода / вывода.

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

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

Например, у вас может быть программа, которая сидит в цикле чтения целых чисел, используя scanf("%d", &n), и вы обнаружили, что в первый раз, когда пользователь вводит нецифровой символ, например 'x', программа переходит в бесконечный цикл.

Столкнувшись с этой ситуацией, я считаю, что у вас есть три варианта выбора:

  1. Как-то очистить ввод (если не использовать fflush(stdin), затем позвонив getchar читать символы, пока \n, как это часто рекомендуется).
  2. Скажите пользователю, чтобы он не набирал нецифровые символы, если предполагается использование цифр.
  3. Используйте что-нибудь кроме scanfчитать ввод.

Теперь, если вы новичок, scanf кажется самым простым способом читать вводимые данные, поэтому вариант №3 пугает и труден. Но №2 кажется настоящим отговоркой, потому что все знают, что недружественные пользователю компьютерные программы - это проблема, поэтому было бы неплохо сделать лучше. Поэтому слишком много начинающих программистов загоняют в угол, чувствуя, что у них нет другого выбора, кроме как сделать №1. Они более или менее должны вводить данные, используяscanf, что означает, что он застрянет при неправильном вводе, а это означает, что им нужно найти способ сбросить неправильный ввод, а это означает, что они испытывают сильное искушение использовать fflush(stdin).

Я хотел бы призвать всех начинающих программистов пойти на другой набор компромиссов:

  1. На самых ранних этапах вашей карьеры программиста на C, прежде чем вы научитесь использовать что-либо, кроме scanf, только не беспокойтесь о неверном вводе. В самом деле. Продолжайте и используйте отговорку №2 выше. Подумайте об этом так: вы новичок, есть много вещей, которые вы еще не умеете делать, и одна из вещей, которую вы еще не знаете, - это изящно обращаться с неожиданным вводом.

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

Или, другими словами, новички, которые все еще не могут использовать scanf не стесняйтесь использовать отговорку №2, и когда они будут готовы, они должны перейти от нее к технике №3, и никто не должен использовать технику №1, чтобы попытаться смыть ввод вообще (и уж точно не с помощью fflush(stdin).

С помощью fflush(stdin)промывка воды похожа на ловлю воды с помощью палки в форме буквы "S".

А помогать людям смывать ввод каким-то "лучшим" способом - это все равно, что бросаться к лозоискателю с S-образной палкой и говорить: "Нет, нет, вы делаете это неправильно, вам нужно использовать Y-образную палку!".

Другими словами, настоящая проблема не в том, fflush(stdin)не работает. Вызовfflush(stdin)является признаком основной проблемы. Зачем вам вообще нужно "сбрасывать" ввод? Это твоя проблема.

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

Ни один из существующих ответов не указывает на ключевой аспект проблемы.

Если вы обнаружите, что хотите «очистить буфер ввода», вы, вероятно, пишете интерактивную программу командной строки, и было бы точнее сказать, что вы хотите отбросить символы из текущей строки ввода, которые у вас отсутствуют. Уже не читал.

Это не то, что нужно.Библиотеки C, поддерживающие использование fflushво входном потоке задокументируйте его либо как бездействие, либо как отбрасывание буферизованных данных, которые были прочитаны из базового файла, но не переданы приложению . Это легко может быть больше или меньшечем остальная часть текущей строки. Вероятно, во многих случаях это срабатывает случайно, потому что драйвер терминала (в режиме по умолчанию) предоставляет ввод в интерактивную программу командной строки по одной строке за раз. Однако в тот момент, когда вы пытаетесь передать данные в свою программу из реального файла на диске (возможно, для автоматического тестирования), ядро ​​и библиотека C переключатся на буферизацию данных большими «блоками» (часто от 4 до 8 КБ) без каких-либо ограничений. связь с границами строк, и вам будет интересно, почему ваша программа обрабатывает первую строку файла, а затем пропускает несколько десятков строк и выбирает середину некоторой, по-видимому, случайной строки ниже. Или, если вы решите протестировать свою программу на очень долгом строка, набранная вручную, то драйвер терминала не сможет передать программе сразу всю строку и fflush(stdin) не пропущу все это.

Так что же делать вместо этого? Подход, который я предпочитаю, заключается в том, что если вы обрабатываете ввод по одной строке за раз, то читаете всю строку сразу . В библиотеке C есть функции специально для этого: fgets (в C90, полностью переносимый, но все же заставляющий обрабатывать очень длинные строки кусками) и getline (Специфично для POSIX, но будет управлять malloced, чтобы вы могли обрабатывать длинные строки сразу, независимо от их длины). Обычно существует прямой перевод из кода, который обрабатывает «текущую строку» непосредственно из stdin, в код, обрабатывающий строку, содержащую «текущую строку».

Цитата из POSIX:

Для потока, открытого для чтения, если файл еще не находится в EOF, и файл доступен для поиска, смещение файла базового описания открытого файла должно быть установлено в файловую позицию потока, а любые символы отбрасываются назад. на поток с помощью ungetc() или ungetwc(), которые впоследствии не были прочитаны из потока, должны быть сброшены (без дальнейшего изменения смещения файла).

Обратите внимание, что терминал не способен искать.

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

Согласно стандарту C это неопределенное поведение. Однако некоторые компиляторы, такие как Microsoft visual studio, позволяют это.

Другие вопросы по тегам