В последнее время я видел людей, пытающихся читать такие файлы во многих сообщениях:
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char **argv)
{
char *path = "stdin";
FILE *fp = argc > 1 ? fopen(path=argv[1], "r") : stdin;
if( fp == NULL ) {
perror(path);
return EXIT_FAILURE;
}
while( !feof(fp) ) { /* THIS IS WRONG */
/* Read and process data from file… */
}
if( fclose(fp) != 0 ) {
perror(path);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Что не так с этим циклом?
feof()
для управления цикломОтветы:
Я хотел бы представить абстрактную перспективу высокого уровня.
Параллельность и одновременность
Операции ввода / вывода взаимодействуют со средой. Среда не является частью вашей программы и не находится под вашим контролем. Среда действительно существует "одновременно" с вашей программой. Как и в случае с другими вещами, вопросы о «текущем состоянии» не имеют смысла: не существует понятия «одновременности» между одновременными событиями. Многие свойства государства просто не существуют одновременно.
Позвольте мне уточнить это: предположим, вы хотите спросить: «У вас есть больше данных». Вы можете запросить это у параллельного контейнера или вашей системы ввода-вывода. Но ответ, как правило, бездействующий и, следовательно, бессмысленный. Так что, если контейнер скажет «да» - к тому времени, когда вы попытаетесь прочитать, у него больше не будет данных. Точно так же, если ответ «нет», к тому времени, когда вы попытаетесь прочитать, данные, возможно, уже поступили. Вывод таков :нет такого свойства, как «У меня есть данные», так как вы не можете действовать осмысленно в ответ на любой возможный ответ. (Ситуация несколько лучше с буферизованным вводом, где вы можете получить «да, у меня есть данные», что является своего рода гарантией, но вам все равно придется иметь дело с противоположным случаем. И с выводом ситуации это так же плохо, как я описал: вы никогда не знаете, заполнен ли этот диск или сетевой буфер.)
Таким образом , мы приходим к выводу , что это невозможно, и в самом деле ип разумного , чтобы задать систему ввода / вывода ли он будет в состоянии выполнить операцию ввода / вывода. Единственный возможный способ взаимодействия с ним (так же, как с параллельным контейнером) - это попытаться выполнить операцию и проверить, была ли она успешной или неудачной. В тот момент, когда вы взаимодействуете со средой, тогда и только тогда вы можете узнать, действительно ли взаимодействие было возможно, и в этот момент вы должны посвятить себя выполнению взаимодействия. (Это «точка синхронизации», если хотите.)
EOF
Теперь мы добрались до EOF. EOF - это ответ, который вы получаете от попытки ввода-вывода. Это означает, что вы пытались что-то прочитать или записать, но при этом вам не удалось прочитать или записать какие-либо данные, и вместо этого был обнаружен конец ввода или вывода. Это верно практически для всех API ввода-вывода, будь то стандартная библиотека C, iostreams C ++ или другие библиотеки. Пока операции ввода-вывода успешны, вы просто не можете знать, будут ли дальнейшие будущие операции успешными. Вы всегда должны сначала попробовать операцию, а затем ответить на успех или неудачу.
Примеры
В каждом из примеров обратите внимание на то, что мы сначала пытаемся выполнить операцию ввода-вывода, а затем используем результат, если он действителен. Отметим далее, что мы всегда должны использовать результат операции ввода-вывода, хотя результат принимает разные формы и формы в каждом примере.
C stdio, читайте из файла:
Результат, который мы должны использовать
n
, это количество прочитанных элементов (которое может быть равным нулю).C STDIO,
scanf
:Результатом, который мы должны использовать, является возвращаемое значение
scanf
количества преобразованных элементов.C ++, извлечение в формате iostreams:
Результат, который мы должны использовать,
std::cin
сам по себе, который может быть оценен в логическом контексте и говорит нам, находится ли поток вgood()
состоянии.C ++, iostreams getline:
Результат, который мы должны использовать, снова
std::cin
, как и прежде.POSIX,
write(2)
чтобы очистить буфер:В результате мы используем
k
количество записанных байтов. Дело в том, что мы можем знать только, сколько байтов было записано после операции записи.POSIX
getline()
Результат, который мы должны использовать
nbytes
, это число байтов до новой строки (включая EOF, если файл не заканчивался новой строкой).Обратите внимание, что функция явно возвращает
-1
(а не EOF!), Когда возникает ошибка или она достигает EOF.Вы можете заметить, что мы очень редко произносим слово «EOF». Обычно мы обнаруживаем состояние ошибки другим способом, который более интересен для нас (например, невозможность выполнить столько операций ввода-вывода, сколько мы хотели). В каждом примере есть некоторая функция API, которая может явно сообщить нам, что с состоянием EOF было обнаружено, но на самом деле это не очень полезная часть информации. Это гораздо больше деталей, чем мы часто заботимся. Важно то, был ли ввод / вывод успешным, в большей степени, чем как он провалился.
Последний пример, который фактически запрашивает состояние EOF: предположим, что у вас есть строка и вы хотите проверить, что она представляет собой целое число полностью, без лишних битов в конце, кроме пробелов. Используя C ++ iostreams, это выглядит так:
Мы используем два результата здесь. Первый -
iss
это сам объект потока, чтобы проверить, что отформатированное извлечение выполненоvalue
успешно. Но затем, после использования пустого пространства, мы выполняем еще одну операцию ввода-выводаiss.get()
и ожидаем, что она завершится с ошибкой как EOF, что является случаем, если вся строка уже была использована форматированным извлечением.В стандартной библиотеке C вы можете добиться чего-то похожего с
strto*l
функциями, проверив, что указатель конца достиг конца входной строки.Ответ
while(!feof)
неправильно, потому что он проверяет что-то, что не имеет значения и не может проверить то, что вам нужно знать. В результате вы ошибочно выполняете код, который предполагает, что он обращается к данным, которые были успешно прочитаны, хотя на самом деле этого никогда не происходило.источник
feof()
не "спрашивает систему ввода / вывода, есть ли у нее больше данных".feof()
в соответствии с man-страницей (Linux) : «проверяет индикатор конца файла для потока, на который указывает поток, и возвращает ненулевое значение, если оно установлено». (также,clearerr()
единственный способ сбросить этот индикатор - явный вызов ); В этом отношении ответ Уильяма Перселла намного лучше.Это неправильно, потому что (при отсутствии ошибки чтения) он входит в цикл еще раз, чем ожидает автор. Если есть ошибка чтения, цикл никогда не завершается.
Рассмотрим следующий код:
Эта программа будет последовательно печатать на единицу больше, чем количество символов во входном потоке (при условии отсутствия ошибок чтения). Рассмотрим случай, когда входной поток пуст:
В этом случае
feof()
вызывается до того, как какие-либо данные были прочитаны, поэтому возвращает false. Цикл вводится,fgetc()
вызывается (и возвращаетсяEOF
), и счет увеличивается. Затемfeof()
вызывается и возвращает истину, вызывая прерывание цикла.Это происходит во всех таких случаях.
feof()
не возвращает true, пока после чтения в потоке не встретится конец файла. Цельfeof()
НЕ в том, чтобы проверить, достигнет ли следующее чтение конца файла. Цельfeof()
состоит в том, чтобы отличить ошибку чтения от достижения конца файла. Еслиfread()
возвращается 0, вы должны использоватьfeof
/,ferror
чтобы решить, была ли обнаружена ошибка или все данные были использованы. Аналогично, еслиfgetc
возвращаетсяEOF
.feof()
полезно только после того, как fread вернул ноль илиfgetc
вернулсяEOF
. Прежде чем это произойдет,feof()
всегда будет возвращать 0.Всегда необходимо проверять возвращаемое значение чтения (или
fread()
, или anfscanf()
, или anfgetc()
) перед вызовомfeof()
.Еще хуже, рассмотрим случай, когда происходит ошибка чтения. В этом случае
fgetc()
возвращаетEOF
,feof()
возвращает ложь, и цикл никогда не заканчивается. Во всех случаях, когдаwhile(!feof(p))
это используется, должна быть, по крайней мере, проверка внутри циклаferror()
, или, по крайней мере, условие while должно быть заменено,while(!feof(p) && !ferror(p))
или существует очень реальная возможность бесконечного цикла, вероятно, извергающего все виды мусора как неверные данные обрабатываются.Итак, в заключение, хотя я не могу с уверенностью утверждать, что никогда не бывает ситуации, в которой может быть семантически правильно написать «
while(!feof(f))
» (хотя должна быть другая проверка внутри цикла с разрывом, чтобы избежать бесконечного цикла при ошибке чтения ), это тот случай, когда это почти наверняка всегда неправильно. И даже если когда-либо возникнет случай, когда он будет правильным, это настолько идиоматически неправильно, что это не будет правильным способом написания кода. Любой, кто увидит этот код, должен сразу же подумать и сказать: «Это ошибка». И, возможно, ударить автора (если автор не ваш начальник, в этом случае рекомендуется усмотрение.)источник
feof(file) || ferror(file)
, что и он, поэтому он сильно отличается. Но этот вопрос не предназначен для применения к C ++.Нет, это не всегда неправильно. Если ваше условие цикла "пока мы не пытались прочитать конец файла", тогда вы используете
while (!feof(f))
. Это, однако, не является общим условием цикла - обычно вы хотите проверить что-то еще (например, «могу я прочитать больше»).while (!feof(f))
не неправильно, просто используется неправильно.источник
f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }
или (собираюсь проверить это)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
while(!eof(f))
feof
не относится к обнаружению конца файла; речь идет об определении того, было ли чтение коротким из-за ошибки или из-за исчерпания ввода.feof()
указывает, пытался ли кто-нибудь прочитать за концом файла. Это означает, что у него мало прогнозирующего эффекта: если оно истинно, вы уверены, что следующая операция ввода потерпит неудачу (вы не уверены, что предыдущая провалилась, кстати,), но если она ложна, вы не уверены, что следующий ввод операция будет успешной. Более того, операции ввода могут завершаться неудачей по другим причинам, кроме конца файла (ошибка форматирования для форматированного ввода, ошибка чистого ввода-вывода - сбой диска, тайм-аут сети - для всех типов ввода), поэтому даже если вы можете предвидеть конец файла (и любой, кто пытался внедрить Ada one, который является прогностическим, скажет вам, что он может быть сложным, если вам нужно пропустить пробелы, и что это оказывает нежелательное влияние на интерактивные устройства - иногда вынуждая вводить следующее строка перед началом обработки предыдущего),Таким образом, правильная идиома в C состоит в том, чтобы выполнить цикл с успешным выполнением операции ввода-вывода в качестве условия цикла, а затем проверить причину сбоя. Например:
источник
else
не представляется возможным сsizeof(line) >= 2
и ,fgets(line, sizeof(line), file)
но возможно с патологическимsize <= 0
иfgets(line, size, file)
. Может быть, даже возможно сsizeof(line) == 1
.feof(f)
ничего не ПРОГНОЗИТЬ. В нем говорится, что ПРЕДЫДУЩАЯ операция достигла конца файла. Ни больше ни меньше. И если предыдущей операции не было (только что открыли), она не сообщает о конце файла, даже если файл был пуст для начала. Таким образом, кроме объяснения параллелизма в другом ответе выше, я не думаю, что есть какая-либо причина, чтобы не зацикливатьсяfeof(f)
.feof()
не очень интуитивно понятно По моему очень скромному мнению, состояниеFILE
конца файла должно быть установлено равным,true
если какая-либо операция чтения приводит к достижению конца файла. Вместо этого вы должны вручную проверять, был ли достигнут конец файла после каждой операции чтения. Например, что-то вроде этого будет работать, если чтение из текстового файла с помощьюfgetc()
:Было бы замечательно, если бы что-то вроде этого работало вместо этого:
источник
printf("%c", fgetc(in));
? Это неопределенное поведение.fgetc()
возвращаетсяint
, нетchar
.while( (c = getchar()) != EOF)
очень похожа на это.while( (c = getchar()) != EOF)
работает на одном из моих компьютеров под управлением GNU C 10.1.0, но не работает на Raspberry Pi 4 под управлением GNU C 9.3.0. На моем RPi4 он не определяет конец файла и просто продолжает работу.char c
наint c
работу! Спасибо!!