Преобразование комментариев в ответ и расширение их, поскольку проблема периодически появляется.
Стандартные 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 . Однако это работает ниже стандартного уровня библиотеки ввода / вывода.