Використання fflush (stdin)


77

Тож швидкий пошук у Google fflush(stdin)для очищення буфера введення виявляє численні веб-сайти, що застерігають від його використання. І все ж саме так мій професор КС навчив клас це робити.

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



8
І Windows, і Linux визначають поведінку fflush()вхідного потоку і навіть визначають її однаково (чудо чудес). Стандарти POSIX, C та C ++ fflush()не визначають поведінку, але жоден із них не заважає системі визначати її. Якщо ви кодуєте для максимальної портативності, уникайте fflush(stdin); якщо ви кодуєте платформи, що визначають поведінку, використовуйте її, але майте на увазі, що вона не є портативною.
Джонатан Леффлер

Cygwin - приклад досить поширеної платформи, на якій fflush(stdin);не можна очистити вхідні дані.
MM

2
Це також залежить від того, що саме ви плануєте fflush(stdin)зробити.
Кіт Томпсон,

@JonathanLeffler Документ windows каже If the stream was opened in read mode, or if the stream has no buffer, the call to fflush has no effect, and any buffer is retained, а документ Linux каже, For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.що це не зовсім так, Windows зберігає буфер, а Linux відкидає буфер.
ssbssa

Відповіді:


76

Просто: це невизначена поведінка, оскільки fflushпризначена для виклику у вихідному потоці. Це витяг із стандарту С:

int fflush (FILE * ostream);

ostream вказує на вихідний потік або потік оновлення, в якому не було введено останню операцію, функція fflush викликає запис будь-яких неписаних даних для цього потоку, що доставляються в хостове середовище, у файл; в іншому випадку поведінка не визначена.

Тож справа не в тому, наскільки це погано. fflush(stdin)є явно неправильним , і ви не повинні ним користуватися, ніколи .


16
@BlueRaja: тут є захист за помилку новачка, але немає захисту для вчителя, який поширює неправильні знання! Будь-яке посилання fflushчітко дає зрозуміти, що воно призначене для вихідних потоків прямо в першому абзаці, для цього не потрібно запам’ятовувати стандарт C!
Елі Бендерський

6
@Eli: Ніхто не може все знати. Процесор ніколи не дізнається про свою помилку, поки хтось не скаже йому ... Я fflush(stdin)роками користувався, поки не виявив, що це UB (випадково)
BlueRaja - Danny Pflughoeft

4
Помилка, чи не слід звичайно переглядати документацію щодо функції, перш ніж її використовувати? Особливо професор?
Alex Budovski

6
Іншим моментом захисту є наступна частина сторінки (різні версії glibc в Linux): "Для потоків вводу fflush()відкидає будь-які буферизовані дані, отримані з базового файлу, але не споживані додатком. Відкритий статус потоку не змінюється ". Хоча це UB, деякі реалізації, здається, дають гарантії, не згадуючи його статус щодо стандарту.
Даніель Фішер,

4
Існує ще один аспект, про який я рідко бачу, - fflush(stdin)це набагато гірше, ніж просто поведінка, визначена реалізацією. Навіть якби це працювало так, як задумали більшість людей, це було б жахливо. Уявіть, якщо stdin - це не хтось тупо набирає введення, а надходить з іншої програми або перенаправлення оболонки: він прочитає початок файлу, а потім просто стерть решту. Дуже глупо думати, що stdin - це завжди щось таке повільне, як людський оператор.
Рафаель Лерм,

41

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

Стандартні 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

Приклад виводу

Цей результат було отримано як на Ubuntu 14.04 LTS, так і на Mac OS X 10.11.2. На моє розуміння, це суперечить тому, що сказано в посібнику Linux. Якби fflush(stdin)операція спрацювала, мені довелося б набрати новий рядок тексту, щоб отримати інформацію для getchar()прочитання другого .

Враховуючи те, що говорить стандарт POSIX, можливо, потрібна краща демонстрація, і документація Linux повинна бути уточнена.

demo-fflush2.c

Приклад виводу

Зверніть увагу, що /etc/passwdце доступний файл. На Ubuntu перший рядок виглядає так:

У Mac OS X перші 4 рядки виглядають так:

Іншими словами, у верхній частині /etc/passwdфайлу Mac OS X є коментар . Рядки, що не коментують, відповідають звичайному макету, тому rootзапис:

Ubuntu 14.04 LTS:

Mac OS X 10.11.2:

Поведінка 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 . Однак це працює нижче стандартного рівня бібліотеки вводу-виводу.


Це, мабуть, краща відповідь у контексті того, про що просив ОП, хоча прийнята не є помилковою. Чітко показати, що він не відповідає стандартам з одного боку, але показати, що він може бути правильно використаний для конкретної реалізації, з іншого. +1
RobertS підтримує Моніку Челліо

21

Відповідно до стандарту, fflushможе використовуватися лише з вихідними буферами, і, очевидно stdin, не є одним. Однак деякі стандартні бібліотеки C надають можливість використовувати їх fflush(stdin)як розширення. У такому випадку ви можете використовувати його, але це вплине на портативність, тож ви більше не зможете користуватися жодною стандартною бібліотекою C, яка відповідає стандартам, і очікувати однакових результатів.


8

Я вважаю, що вам ніколи не слід дзвонити fflush(stdin)з тієї простої причини, що ви ніколи не повинні вважати за потрібне спробувати змити введення. Реально, існує лише одна причина, через яку ви могли б подумати, що вам довелося це зробити, і це: пройти повз поганий вхід, який scanfзастряг.

Наприклад, у вас може бути програма, яка сидить у циклі, читаючи цілі числа scanf("%d", &n), і ви виявили, що коли користувач вперше вводить нецифровий символ, наприклад 'x', програма переходить у нескінченний цикл .

Зіткнувшись із такою ситуацією, я вважаю, що у вас є три варіанти:

  1. Якось змийте введення (якщо не за допомогою fflush(stdin), то зателефонувавши getcharчитати символи до тих пір \n, як це часто рекомендується).
  2. Скажіть користувачеві не вводити нецифрові символи, коли очікуються цифри.
  3. Використовуйте щось інше, ніж scanfдля читання вводу .

Зараз, якщо ви новачок, scanf здається, це найпростіший спосіб прочитати введені дані, тому вибір №3 є страшним і складним. Але №2 здається справжнім викидом, бо всі знають, що недоброзичливі для користувача комп’ютерні програми - це проблема, тому було б непогано зробити краще. Тому занадто багато початківців програмістів зафарбовуються в кут, відчуваючи, що їм нічого не залишається, як зробити No1. Вони більш-менш мусять робити введення, використовуючи scanf, це означає, що він застрягне на поганому введенні, це означає, що вони повинні знайти спосіб очищення поганого вводу, тобто вони дуже спокушаються використовувати fflush(stdin).

Я хотів би заохотити всіх починаючих програмістів робити різні компроміси:

  1. На самих ранніх етапах вашої кар’єри програмування C, перш ніж вам буде зручно користуватися чимось іншим scanf, просто не турбуйтеся про поганий вхід . Дійсно. Вперед і скористайтеся копією No2 вище. Подумайте про це так: Ви новачок, є багато речей, які ви ще не знаєте, як робити, і одна з речей, які ви поки не знаєте, як це: витончено попрацюйте з несподіваним вкладом.

  2. Як тільки ви зможете, навчіться робити введення за допомогою інших функційscanf . На цьому етапі ви можете почати витончено мати справу з поганим введенням, і у вас буде набагато більше, набагато кращих методів, доступних для вас, для яких зовсім не буде потрібно намагатися очистити поганий вхід.

Або, іншими словами, початківці, які все ще застрягли у використанні, scanfповинні сміливо використовувати копію №2, і коли вони будуть готові, вони повинні перейти з там до техніки №3, і ніхто не повинен використовувати техніку №1, щоб спробувати флеш-введення взагалі (і, звичайно, не з fflush(stdin).


Один момент до nitpick, тому що це трохи неоднозначно, на мою думку, і хтось може вас помилити: " Змийте введення якось (якщо не за допомогою fflush(stdin), то зателефонувавши getcharчитати символи до \n, як це часто рекомендується). " - Один дзвінок до getchar()doesn Не читає символи , поки їх не знайде \n. Якщо символів декілька, один дзвінок getchar()отримує лише останній введений символ, не всі до, а також не включаючи новий рядок. Крім того, getchar()також може споживати новий рядок.
RobertS підтримує Моніку Челліо

2

Використання fflush(stdin)для змивання вводу - це щось на кшталт радіодеструкції води за допомогою палички у формі букви "S".

А допомогти людям змити введення якимось «кращим» способом - це щось на кшталт того, як кинутися до радиестера на S-stick і сказати «Ні, ні, ви робите це неправильно, вам потрібно використовувати Y-подібну палицю!».

Іншими словами, справжня проблема полягає не в тому, fflush(stdin)що не працює. Телефонний дзвінок fflush(stdin)є симптомом основної проблеми. Чому вам взагалі доводиться «змивати» введення? Це ваша проблема.

І, як правило, основною проблемою є те, що ви використовуєте scanf, в одному з багатьох заплутаних режимів, який несподівано залишає нові рядки або інші пробіли на вході. Отже, найкраща довгострокова відповідь - навчитися робити введення, використовуючи кращі, ніжscanf .


Хоча, мабуть, ви забули про цю відповідь stackoverflow.com/a/58884121/918959
Антті Хаапала

@AnttiHaapala Дякую за вказівник, але, ні, я не забув; обидві відповіді пов’язані в моїх замітках на цю тему. Є ще кілька хороших, канонічних відповідей на stackoverflow.com/questions/34219549 .
Самміт Стіва

Я маю на увазі, що вони мають одне і те ж питання: D
Antti Haapala

@AnttiHaapala Так, я розумію. Коли я опублікував другий, SO запитав мене: "У вас вже є відповідь на це питання, ви впевнені, що хочете відповісти ще раз?", А я відповів: "Так". Для цих вічних питань я завжди намагаюся знайти різні / кращі / альтернативні способи відповіді на них. (Інший приклад - stackoverflow.com/questions/949433 .)
Стів Саміт

1

Цитата з POSIX :

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

Зверніть увагу, що термінал не здатний шукати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.