Использование fflush (stdin) - PullRequest
66 голосов
/ 05 июня 2010

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

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

Ответы [ 4 ]

66 голосов
/ 05 июня 2010

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

int fflush (FILE * ostream);

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

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

33 голосов
/ 13 декабря 2015

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

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

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

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

§7.21.5.2 Функция fflush

¶2 Если 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 . Однако это работает ниже стандартного уровня библиотеки ввода / вывода.

18 голосов
/ 05 июня 2010

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

1 голос
/ 18 мая 2018

Цитата из POSIX :

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

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

...