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

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

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

Ответ 1

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

int fflush (FILE * ostream);

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

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

Ответ 2

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

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

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

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

§7.21.5.2 Функция fflush

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

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

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

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

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

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

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

M.M примечания:

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

Вот почему в этой версии ответа comment отмечается "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) составляет undefined. POSIX добавляет квалификатор ', если входной файл не доступен для поиска', который не является терминалом. Поведение не совпадает с поведением Microsoft.

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

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

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

Ответ 3

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

Ответ 4

Цитата из POSIX:

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

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

Ответ 5

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

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