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