Каждый поток имеет «индикатор ошибки, который записывает, произошла ли ошибка чтения / записи».
Устанавливается, как правило, редко, различными функциями: fgetc(), fflush(), fseek(), ....
Очищается различными функциями: rewind(), clearerr(), fopen(), ....
int ferror(FILE *stream) сообщает о состоянии.
The
ferrorfunction returns nonzero if and only if the error indicator is set forstream.
В этом случае обязательно только что произошла ошибка ввода.
if (!ferror(istream)) {
int ch = fgetc(istream);
if (ch == EOF && ferror(istream)) {
puts("Input error just occurred");
}
}
Изучая fgetc() глубже, fgetc() возвращает EOF не потому, что был установлен индикатор ошибки, а потому, что «Если возникает ошибка чтения» или причины, связанные с концом файла, 1. Обычно, когда возникает ошибка (например, ошибка четности в последовательном потоке), код не продолжает чтение, не очистив ошибку, но подумайте, что произойдет, когда он продолжится.
Я вижу 8 ситуаций: индикатор ошибки устанавливается / очищается до fgetc(), fgetc() возвращает EOF или нет, а следующий ferror() может быть верным или нет.
int e1 = !!ferror(istream);
int eof = fgetc(istream) == EOF;
int e2 = !!ferror(istream);
Предполагая, что UB нет, 5 из 8 возможных, а не 3 неожиданных? особенно допустимый ввод возможен с установленным индикатором ошибки?2
e1 eof e2
0 0 0 Normal reading of valid data
0 0 1 Unexpected
0 1 0 End-of-file
0 1 1 Input error
1 0 0 Unexpected
1 0 1 Normal reading of valid data with error indicator set!
1 1 0 Unexpected
1 1 1 Input error or end-of-file
Если индикатор ошибки установлен перед операцией ввода, все усложняется, и его предварительная очистка упрощает код. Тем не менее, это предотвращает накопление индикатор ошибки.
Если коды не очищают индикатор ошибки заранее и хотят определить, имеет ли вход линия редкую ошибку ввода, кажется, имеет смысл протестировать !feof(), а не ferror() для обнаружения.
Может ли проверка ferror() ввести в заблуждение? или я что-то пропустил по индикатор ошибки?
char buf[80];
for (int i=0; i<(80-1); i++) {
int ch = fgetc(stdin);
if (ch == EOF) {
if (ferror(stdin)) {
puts("Input error or (Prior input error and end of file occurred)"); // ambiguous
}
if (feof(stdin)) {
puts("End of file occurred");
} else {
puts("Input error occurred"); // ferror() test not needed
i = 0; // ignore prior input
}
break;
}
if (ch == '\n') break;
buf[i++] = ch;
}
buf[i] = 0;
Похожие вопросы
Файловые операции с установленным индикатором ошибки
. Этот безответный фокус сосредоточен на накоплении индикатор ошибки без тестирования возвращаемого значения fgetc() (ответы уходят в errno и выставляют флаг ошибки пользователя), а этот пытается просто устранить неоднозначность fgetc().
fgetc (): Достаточно ли просто проверить EOF? не адресует индикатор ошибки, установленный до fgetc().
Аналогичные проблемы применимы к потокам вывода и ввода-вывода, но в этом вопросе основное внимание уделяется потокам ввода.
1int fgetc(FILE *stream) Возврат
If the end-of-file indicator for the stream is set, or if the stream is at end-of-file, the end-of-file indicator for the stream is set and the
fgetcfunction returnsEOF. Otherwise, the fgetc function returns the next character from the input stream pointed to bystream. If a read error occurs, the error indicator for the stream is set and thefgetcfunction returnsEOF. C11dr §7.21.7.1 2
2 По случаям 0-1-0, 1-1-1. Похоже, что UCHAR_MAX == UINT_MAX, unsigned char, можно было бы вернуть и приравнять к EOF, а не из-за ошибки конца файла или ввода.
@NominalAnimal Ближе к стандартам, чем только на практике, поскольку я пытаюсь разгадать загадки индикатор ошибки, которые нужно решить: когда fgetc() возвращает EOF, это связано с недавней ошибкой, концом файла, каким-то широким unsigned char или что-то другое? LL добавил.
FWIW, в glibc это всегда происходит либо из-за обнаружения нового конца файла, либо из-за ошибки, поскольку функции fgetc() / getc() / getchar() никогда не проверяют флаг ошибки.
@NominalAnimal Возвращает ли glibc также EOF для существующего флага конца файла, а не только для нового конца файла?
Нет, он не проверяет наличие флага конца файла. Он попытается прочитать больше из потока. (Это также означает, что даже если ferror() или feof() вернет ненулевое значение, последний вызов fgetc() / getc() / getchar() не обязательно завершился ошибкой; условие могло возникнуть раньше.)
@NominalAnimal Хммм, "не проверяет существующий флаг конца файла" выглядит как несоответствие C11 §7.21.7.1 2. По возможности, пожалуйста, дайте ссылку на ссылку или образец исходного кода для поддержки этого отсутствия проверки.
glibc fgetc() реализован в libio / getc.c: _IO_getc (). Единственная выполняемая проверка - это CHECK_FILE(handle), реализованный в libio / libioP.h как макрос, и обычно оптимизируется. _IO_getc_unlocked () расширяется в libio / libio.h на libio / биты / типы / struct_FILE.h: __ getc_unlocked_body () ...
который является либо прямым поиском в кэше, либо вызовом libio / genops.c: __ uflow (). _IO_UFLOW () разрешает переход к __uflow (). Чеков там нигде не вижу.
Что касается EOF, скомпилируйте и запустите следующее: #include <stdio.h>int main(void) { int c; do { c = fgetc(stdin); if (c == EOF) printf("EOF\n"); } while (c != '.'); return 0; }. Полная остановка . завершает программу. При нажатии Ctrl + D в начале строки печатается строка EOF. Вы можете нажать ее несколько раз: показывает, что EOF не кэшируется (конечно, кроме feof(): он запоминает, был ли EOF замечен). Я проверю, вызывает ли EINTR функцию ferror (), и если да, то добавлю сюда «ответ» с полной программой репликации.
Ага; проверено в Linux на AMD64 / x86-64 с использованием встроенной библиотеки GNU C версии 2.23-0ubuntu10 в Ubuntu 16.04.4 LTS. Программа проверки размещена как "ответ".
Поведение glibc fgetc было изменено в версии 2.28, см. sourceware.org/ml/libc-alpha/2018-08/msg00003.html и sourceware.org/bugzilla/show_bug.cgi?id=1190. Мы наверняка думали, что 7.21.7p2,3 требует «липкого EOF» - изменение описывается как «исправление давней ошибки соответствия C99». (Однако индикатор EOF и индикатор ошибки - это два отдельных бита.)
@zwol Я исследовал пример кода @ Номинальное животное и ответил ниже. IMO, функциональность 1) кажется несоответствующей 2) При вводе с клавиатуры допустимых данных после ^ d, fgetc()очищает флаг конца файла! 3) Все еще изучаю.





Все, что вы говорите, кажется правильным, и ch==EOF && !feof(f) - это правильный способ проверить наличие новых ошибок, не мешая накоплению ошибок.
Assuming no UB, are 5 of the 8 possible and not the 3 unexpected ones? especially is valid input possible with error indicator set?
Говоря конкретно о положениях стандарта, я склонен согласиться с вашим анализом:
Для очистки индикатора ошибки потока указано несколько функций, и fgetc() не входит в их число.. В общем, ни одна из них не является функциями передачи данных. Следовательно, если индикатор ошибки установлен для потока до того, как этот поток будет представлен в fgetc() для чтения, он все равно должен быть установлен, когда эта функция вернется, несмотря на все другие соображения. Это касается следующих случаев: *
1 0 0 Unexpected
1 1 0 Unexpected
1 1 1 Input error or end-of-file
Он также охватывает этот случай в отношении ожидаемого значения индикатора ошибки, хотя не говорит о том, может ли это действительно произойти:
1 0 1 Normal reading of valid data with error indicator set!
fgetc() указан для возврата EOF в каждой ситуации, в которой он указан для установки индикатора конца файла в потоке.. Следовательно, если fgetc() возвращает что-либо, кроме EOF, то при этом вызове он не будет устанавливать индикатор ошибки потока (или конца файла). Это касается следующих случаев:
0 0 0 Normal reading of valid data
0 0 1 Unexpected
С другой стороны, если fgetc()делает возвращает EOF, то впоследствии должен быть установлен либо индикатор конца файла потока, либо его индикатор ошибки. Но стандарт различает эти случаи и указывает, что пользователь может различать их с помощью функций feof() и ferror(). Это касается следующих случаев: *
0 1 0 End-of-file
0 1 1 Input error
Наконец, я согласен с тем, что ни одно поведение fgetc() не зависит от начального состояния индикатора ошибки потока. При условии, что поток изначально не позиционируется в конце и его индикатор конца файла изначально не установлен, «функция fgetc возвращает следующий символ из входного потока, на который указывает поток». Это устанавливает, что это, наиболее интересный случай, на самом деле разрешено:
1 0 1 Normal reading of valid data with error indicator set!
тем не мение, что случай разрешен в аннотации, не означает, что его можно наблюдать на практике. Детали кажутся неопределенными, и я ожидал бы, что они будут зависеть от реализации драйвера, обслуживающего рассматриваемый поток. Вполне возможно, что, столкнувшись с ошибкой, драйвер продолжит сообщать об ошибке при последующих чтениях до тех пор, пока не будет сброшен соответствующим образом, а возможно и дольше. С точки зрения C, это будет интерпретироваться как (дополнительная) ошибка, возникающая при каждом последующем чтении, и ничто в спецификациях языка не предотвращает этого. Даже не использовать одну из функций, очищающих индикатор ошибки потока.
If codes does not clear the error indicator before hand and wants to detect if a line of input had a rare input error, it seems to make sense to test
!feof()and notferror()to detect.Is checking
ferror()potentially misleading? or have I missed something about the error indicator?
Я согласен с тем, что если изначально установлен индикатор ошибки потока, а индикатор конца файла - нет, и чтение его с помощью fgetc() возвращает EOF, тогда ferror() не проводит различий между случаями конца файла и ошибками, тогда как feof() должен.
С другой стороны, можно ли продолжать чтение данного потока после того, как в нем обнаружена ошибка, зависит от реализации и, возможно, от конкретных обстоятельств. Это применимо, даже если индикатор ошибки сброшен с помощью вызова clearerr(), не говоря уже о том, очищен ли индикатор ошибки нет.
* Хотя я согласен с тем, что существует двусмысленность в отношении EOF в случае, если это UCHAR_MAX > INT_MAX, я утверждаю, что это лишь одна из нескольких причин, по которым такая реализация будет проблематичной. Поэтому с практической точки зрения я не считаю такие реализации полностью гипотетическими.
Дополнительный интерес к «продолжению полезного чтения данного потока после обнаружения ошибки» возникает из проверки функций ввода на надежность. Пример: my_getline (). Такие функции при вызове могут иметь или не иметь установленный индикатор ошибки. Предотвращение вызова при обнаружении предыдущей ошибки находится вне контроля функции - точно так же, как fgetc(). Обнаружение ошибки ввода, кажется, лучше полагается на c == EOF && !feof(stdin), чем на c == EOF && ferror(...), поэтому я искал другие хорошие идеи, касающиеся ошибка ввода.
@chux, я согласен с тем, что, выполнив int c = fgetc(s); в соответствующей реализации C, без знания начального состояния потока s, проверка ошибки при чтении через c == EOF && !feof(s) является допустимой и более надежной, чем ее аналог, включающий ferror. Если вместо этого будет использоваться ferror, то сначала необходимо убедиться, что индикатор ошибки потока сброшен. Мне неизвестны альтернативы, принципиально отличные от этих.
Вот очень грубая, очень минимальная программа для изучения поведения библиотеки GNU C по отношению к fgetc(), ferror() и feof(), как запрошено OP в комментарии:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
static volatile sig_atomic_t interrupted = 0;
static void interrupt_handler(int signum)
{
interrupted = 1;
}
static int install_interrupt(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = interrupt_handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return -1;
return 0;
}
int main(void)
{
int n, c;
if (install_interrupt(SIGALRM)) {
fprintf(stderr, "Cannot install SIGALRM handler: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (ferror(stdin)) {
fprintf(stderr, "Standard input is already in error state.\n");
return EXIT_FAILURE;
}
if (feof(stdin)) {
fprintf(stderr, "Standard input is already in end-of-input state.\n");
return EXIT_FAILURE;
}
fprintf(stderr, "Testing stream error state. Please wait.\n");
alarm(1);
c = fgetc(stdin);
if (c != EOF) {
fprintf(stderr, "Stream error state test failed.\n");
return EXIT_FAILURE;
}
fprintf(stderr, "fgetc(stdin) returned EOF.\n");
fprintf(stderr, "ferror(stdin) returns %d.\n", ferror(stdin));
fprintf(stderr, "feof(stdin) returns %d.\n", feof(stdin));
fprintf(stderr, "\n");
fprintf(stderr, "Testing stream end-of-input state. Please press Ctrl+D.\n");
c = fgetc(stdin);
if (c != EOF) {
fprintf(stderr, "fgetc() returned %d; EOF was expected.\n", c);
return EXIT_FAILURE;
}
fprintf(stderr, "fgetc(stdin) returned EOF.\n");
fprintf(stderr, "ferror(stdin) returns %d.\n", ferror(stdin));
fprintf(stderr, "feof(stdin) returns %d.\n", feof(stdin));
if (!ferror(stdin) || !feof(stdin)) {
fprintf(stderr, "Expected error and end-of-file states; aborting.\n");
return EXIT_FAILURE;
}
fprintf(stderr, "\n");
fprintf(stderr, "Testing fgetc() when stream in error and end-of-file state.\n");
fprintf(stderr, "Please type something, then press Enter.\n");
n = 0;
c = fgetc(stdin);
while (c != EOF && c != '\n') {
n++;
c = fgetc(stdin);
}
if (c == EOF) {
fprintf(stderr, "Further input is not possible.\n");
return EXIT_FAILURE;
} else
fprintf(stderr, "Further input is possible: %d characters (including Enter) read.\n", n + 1);
return EXIT_SUCCESS;
}
Когда я компилирую и запускаю вышеуказанное в Linux, программа выведет
Testing stream error state. Please wait.
fgetc(stdin) returned EOF.
ferror(stdin) returns 1.
feof(stdin) returns 0.
Состояние ошибки было вызвано прерыванием доставки сигнала при вызове fgetc(stdin). Как видите, ferror(stdin) действительно возвращает ненулевое значение. Обратите внимание, что feof(stdin) возвращает 0.
Вывод продолжается:
Testing stream end-of-input state. Please press Ctrl+D.
Нажатие Ctrl + C дает результат
fgetc(stdin) returned EOF.
ferror(stdin) returns 1.
feof(stdin) returns 1.
На этом этапе стандартный ввод находится как в состоянии ошибки, так и в состоянии конца файла. Вывод продолжается:
Testing fgetc() when stream in error and end-of-file state.
Please type something, then press Enter.
Если теперь ввести, скажем, OKEnter, мы получим
Further input is possible: 3 characters (including Enter) read.
Это доказывает, что по крайней мере реализация библиотеки GNU C вообще не проверяет ошибку потока или состояние конца файла. Он просто попытается прочитать больше данных (используя базовую операцию read() в системах POSIXy).
Я также нашел похожие результаты, но склонен думать, что это не соответствует стандарту. (IOWs, ошибка в этой версии). Я также заметил, что флаг конца файла был сброшен вызовом fgetc() с 5 клавишами "x\n", Ctrl d, последовательность ".\n" с ключом '.'.
@chux: В системах POSIXy поведение EOF имеет смысл, потому что если файл будет добавлен после того, как мы прочитаем его до конца, мы захотим получить добавленные данные; и поскольку нет функции cleareof(), повторное открытие файла было бы единственным способом сделать это в противном случае. Соответствует ли что-либо из этого стандартам, у меня нет мнения: я решил держаться подальше от language-lawyer. (Если это ошибка в glibc, ее исправление - это донкихотская борьба против людей, которые считают, что знают стандарты лучше, чем кто-либо другой. Не стоит усилий, ИМО.)
Re: "cleareof() нет". Разве clearerr(istream) не будет достаточно вместо повторного открытия файла? IAC, спасибо за изучение углов C I / O.
@NominalAnimal Для записи: если вы считаете, что нашли ошибку в glibc, и не хотите спорить об этом с Джозефом и / или Полом Эггертом, вы можете написать мне по электронной почте, и я сделаю это. Ульрих больше не участвует.
@zwol: Да, libio / getc.c: _IO_getc () и libio / getc_u.c: __ getc_unlocked () по-прежнему не имеют проверки _IO_ferror_unlocked() в HEAD. Я бы рекомендовал превратить _IO_getc_unlocked() в функцию, которая выполняет проверку и возвращает __getc_unlocked_body(). Также RFC для разработчиков: возможно, измените _IO_EOF_SEEN в libio / bits / types / struct_FILE.h на 0x0030, чтобы feof() возвращал ненулевое значение, если eof или ошибка.
@NominalAnimal Спасибо. Я раскрою это подробнее на выходных. Как указывает Джон Боллинджер, для стандарта не требуется липкий индикатор ошибка, а только липкий индикатор EOF. Думаю, _IO_EOF_SEEN нельзя изменить, не нарушив ABI, но, вероятно, есть еще кое-что, что мы могли бы сделать с тем же эффектом.
@zwol: Не беспокойся, это было всего лишь предложение. ИМХО стандарт тогда басовый: нет практической необходимости, чтобы EOF был липким, но если ошибка не липкий, тогда есть реальный риск случайно пропущенных ошибок. Мне так хотелось бы, чтобы среди разработчиков стандарта C было больше инженеров-программистов и меньше ученых-программистов (и представителей компаний); чтобы преодолеть разрыв между теорией и практикой.
Я так понимаю стандарт, что он явно не говорит, что fgetc разрешено возвращать значение, отличное от EOF, если индикатор ошибки уже был установлен в потоке при входе, но он также не говорит явно, что это не могу. Я сочувствую наблюдению Nominal Animal (которое я извлечу из комментариев к его ответу на случай, если он будет удален или перемещен в чат; позвольте мне на мгновение потрогать свой личный топор и заметить, что политика обработки комментариев как "эфемерных" вредна и должны быть отменены):
IMHO the standard is then bass-ackwards: there is no practical need for EOF to be sticky, but if error is not sticky, then there is a real risk of accidentally missing errors.
Однако, если все существующие реализации последовательно не рассматривают ошибку как постоянную, изменить поведение будет очень сложно продать комитету. Поэтому прошу у сообщества тесты:
Ниже представлена сокращенная неинтерактивная версия Программа испытаний номинального животного. Он смотрит только на поведение fgetc после ошибки чтения, но не на EOF. Он использует SIGALRM для прерывания чтения вместо Ctrl-C, поэтому вам не нужно ничего делать, кроме как запустить его.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
static _Noreturn void
perror_exit (const char *msg)
{
perror (msg);
exit (1);
}
static void
handler (int unused)
{
}
int
main (void)
{
struct sigaction sa;
int pipefd[2];
FILE *fp;
int ch, pa;
setvbuf (stdout, 0, _IOLBF, 0);
sa.sa_handler = handler;
sa.sa_flags = 0; /* DO interrupt blocking system calls */
sigemptyset (&sa.sa_mask);
if (sigaction (SIGALRM, &sa, 0))
perror_exit ("sigaction");
if (pipe (pipefd))
perror_exit ("pipe");
fp = fdopen (pipefd[0], "r");
if (!fp)
perror_exit ("fdopen");
printf ("before fgetc 1, feof = %d ferror = %d\n",
feof (fp), ferror (fp));
alarm (1);
ch = fgetc (fp);
if (ch == EOF)
printf ("after fgetc 1, ch = EOF feof = %d ferror = %d\n",
feof (fp), ferror (fp));
else
printf ("after fgetc 1, ch = '%c' feof = %d ferror = %d\n",
ch, feof (fp), ferror (fp));
write (pipefd[1], "x", 1);
alarm (1);
ch = fgetc (fp);
pa = alarm (0);
printf ("after fgetc 2, alarm %s\n",
pa ? "did not fire" : "fired");
if (ch == EOF)
printf ("after fgetc 2, ch = EOF feof = %d ferror = %d\n",
feof (fp), ferror (fp));
else
printf ("after fgetc 2, ch = '%c' feof = %d ferror = %d\n",
ch, feof (fp), ferror (fp));
return 0;
}
На всех Unix, которые я могу получить на данный момент, результат этой программы согласуется с наблюдением Джона Боллинджера о том, что
the case of most interest, is in fact allowed:
1 0 1 Normal reading of valid data with error indicator set!
Я особенно хотел бы знать, что эта программа печатает при запуске в альтернативных библиотеках C на базе Linux (например, musl, bionic); Unix-системы, которые не относятся к Linux и не относятся к типу BSD; и Windows. Если у вас есть что-то еще более экзотическое, попробуйте и это. Я отмечаю это сообщение вики сообщества; пожалуйста, отредактируйте его, чтобы добавить результаты теста.
Программа тестирования должна быть приемлемой для любого C89-совместимого компилятора для среды, в которой существует unistd.h, а signal.h определяет sigaction, за исключением одного использования ключевого слова C11 _Noreturn, которое предназначено только для подавления предупреждений. Если ваш компилятор жалуется на _Noreturn, скомпилируйте с -D_Noreturn=; на результаты это не повлияет. Если у вас нет unistd.h, тестовая программа не сделает ничего значимого в вашей среде. Если у вас нет sigaction, вы мая сможете адаптировать программу для использования альтернативных интерфейсов, но вам нужно убедить SIGALRM как-то прервать блокирующий read.
before fgetc 1, feof = 0 ferror = 0
after fgetc 1, ch = EOF feof = 0 ferror = 1
after fgetc 2, alarm did not fire
after fgetc 2, ch = 'x' feof = 0 ferror = 1
(«нормальное чтение достоверных данных с установленным индикатором ошибки»)
.
before fgetc 1, feof = 0 ferror = 0
after fgetc 1, ch = EOF feof = 0 ferror = 1
after fgetc 2, alarm did not fire
after fgetc 2, ch = EOF feof = 0 ferror = 1
(Поведение «липкая ошибка»: fgetc(fp) немедленно возвращает EOF без вызова read, если ferror(fp) истинен при входе)
.
Вы ищете основанный на стандартах ответ (возможно, языковед) или практический ответ?