Перетворення коментарів у відповідь - і їх розширення, оскільки проблема періодично з’являється знову.
Стандартні C та POSIX залишають fflush(stdin)
як невизначену поведінку
Стандарти POSIX , C та C ++ fflush()
прямо вказують, що поведінка невизначена, але жоден із них не заважає системі визначати її.
ISO / IEC 9899: 2011 - стандарт C11 - говорить:
§7.21.5.2 Функція змиву
¶2 Якщо stream
вказує на вихідний потік або потік оновлення, в якому не було введено останню операцію, fflush
функція змушує будь-які неписані дані для цього потоку, що доставляються в хостове середовище, записуватись у файл; в іншому випадку поведінка не визначена.
POSIX переважно відповідає стандарту C, але він позначає цей текст як розширення C.
[CX] Для потоку, відкритого для читання, якщо файл ще не має EOF, і файл може шукати, зміщення файлу основного опису відкритого файлу має бути встановлене в позицію файлу потоку та символи, відсунуті назад у потік ungetc()
або ungetwc()
не прочитані згодом із потоку, повинні бути відкинуті (без подальшого зміни зміщення файлу).
Зверніть увагу, що термінали не здатні шукати; ні труби, ні розетки.
Microsoft визначає поведінку fflush(stdin)
Microsoft та середовище виконання Visual Studio визначає визначення поведінки fflush()
вхідного потоку.
Якщо потік відкритий для введення, fflush
очищає вміст буфера.
Примітки М.М . :
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
Іншими словами, у верхній частині /etc/passwd
файлу Mac OS X є коментар . Рядки, що не коментують, відповідають звичайному макету, тому 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, яка набагато ретельніше сформульована. Відповідно до стандарту С поведінка користувача fflush(stdin)
не визначена. POSIX додає кваліфікатор "якщо вхідний файл не можна шукати", а термінал не є. Поведінка не така, як у Microsoft.
Отже, портативний код не використовує fflush(stdin)
. Код, прив’язаний до платформи Microsoft, може використовувати його, і він буде працювати, але остерігайтеся проблем із перенесенням.
POSIX-спосіб відкинути непрочитані дані терміналу з дескриптора файлу
Стандартний спосіб відхилення непрочитаної інформації з дескриптора файлу терміналу (на відміну від потоку файлів, наприклад stdin
) проілюстрований у розділі Як я можу змити непрочитані дані з черги вводу tty в системі Unix . Однак це працює нижче стандартного рівня бібліотеки вводу-виводу.