Почему glibc fclose (NULL) вызывает ошибку сегментации вместо возврата ошибки?

Согласно man page fclose(3):

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

После успешного завершения возвращается 0. В противном случае возвращается EOF, и глобальная переменная errno установлена ​​для указания ошибки. В любом случае доступ (включая другой вызов fclose()) к потоку приводит к undefined.

ОШИБКИ

EBADF Недопустимый дескриптор файла, лежащий в основе fp.

Функция fclose() также может выйти из строя и установить errno для любой из ошибок для подпрограмм close(2), write(2) или fflush(3).

Конечно, fclose(NULL) должен потерпеть неудачу, но я ожидаю, что он вернется с errno, как правило, вместо того, чтобы умереть непосредственно по сегментации. Есть ли причина такого поведения?

Спасибо заранее.

ОБНОВЛЕНИЕ: я поставлю здесь свой код (особенно я пытаюсь strerror()).

FILE *not_exist = NULL;

not_exist = fopen("nonexist", "r");
if(not_exist == NULL){
    printError(errno);
}

if(fclose(not_exist) == EOF){
    printError(errno);
}

Ответы

Ответ 1

fclose требуется в качестве аргумента a FILE указатель, полученный либо fopen, один из стандартных потоков stdin, stdout или stderr, или каким-либо другим способом реализации. Нулевой указатель не является одним из них, поэтому поведение undefined, как и fclose((FILE *)0xdeadbeef). NULL не является специальным в C; кроме того, что он гарантирует сравнение не равным с любым действительным указателем, он точно так же, как и любой другой недопустимый указатель, и его использование вызывает поведение undefined, за исключением случаев, когда интерфейс вы передаете его документам в рамках своего контракта, NULL имеет для него какое-то особое значение.

Кроме того, возврат с ошибкой будет действительным (так как поведение равно undefined), но вредное поведение для реализации, поскольку оно скрывает поведение undefined. Предпочтительный результат вызова поведения undefined всегда является сбоем, поскольку он выделяет ошибку и позволяет исправить ее. Большинство пользователей fclose не проверяют возвращаемое значение ошибки, и я бы сделал ставку, что большинство людей, достаточно глупых, чтобы пройти NULL до fclose, не будут достаточно умны, чтобы проверить возвращаемое значение fclose. Можно было бы аргументировать, что люди должны проверять возвращаемое значение fclose вообще, поскольку окончательный флеш может завершиться неудачно, но это не обязательно для файлов, которые открыты только для чтения, или если fflush был вызван вручную до fclose (что является более умной идиомой, так как легче обрабатывать ошибку, пока вы все еще открываете файл).

Ответ 2

Ошибки, о которых говорит справочная страница, - это ошибки времени выполнения, а не ошибки программирования. Вы не можете просто передать NULL в любой API, ожидающий указателя, и ожидать, что API сделает что-то разумное. Передача указателя NULL к функции, документированной с требованием указателя на данные, является ошибкой.

Связанный вопрос: В C или С++ следует проверить параметры указателя на NULL/nullptr?

Процитировать R. комментарий по одному из ответов на этот вопрос:

... вы, кажется, вводите в заблуждение ошибки, возникающие из-за исключительных условий в операционной среде (fs full, out of memory, network down и т.д.) с ошибками программирования. В первом случае, конечно, надежная программа должна быть способна обрабатывать их изящно. В последнем случае надежная программа не может их перенести.

Ответ 3

fclose(NULL) должен преуспеть. free(NULL) преуспевает, потому что это упрощает запись кода очистки.

К сожалению, это не так, как было определено. Поэтому вы не можете использовать fclose(NULL) в переносных программах. (См. http://pubs.opengroup.org/onlinepubs/9699919799/).

Как уже упоминалось, вы обычно не хотите возврата ошибки, если вы передаете NULL в неправильное место. Вы хотите получить предупреждающее сообщение, по крайней мере, на основе отладки/тестирования. Dereferencing NULL дает вам немедленное предупреждение и возможность собирать обратную линию, которая идентифицирует ошибку программирования:). Пока вы программируете, segfault - это лучшая ошибка, которую вы можете получить. C имеет гораздо более тонкие ошибки, которые требуют гораздо больше времени для отладки...

Можно злоупотреблять ошибками, чтобы повысить устойчивость к ошибкам программирования. Однако, если вы опасаетесь, что сбой программного обеспечения потеряет данные, обратите внимание, что точно так же может произойти, например. если ваше оборудование теряет мощность. Вот почему у нас есть автосохранение (с текстовыми редакторами Unix с двухбуквенными именами, такими как ex и vi). Было бы предпочтительнее, чтобы ваше программное обеспечение выглядело кратковременно, вместо того, чтобы продолжать противоречивое состояние.

Ответ 4

Эта проблема fclose(), похоже, является наследием FreeBSD и была принята некритически как в лагерях Microsoft, так и в Linux.

Но HP, SGI, Solaris и CYGWIN, с другой стороны, все обрабатывают fclose(NULL) разумно. Например, man fclose для CYGWIN, который использует newlib, а не OP glibc, указывает:

  fclose returns 0 if successful (including when FP is NULL or not an open file)

См. fooobar.com/questions/351500/... для соответствующего обсуждения.

Ответ 5

Я думаю, что manpage рассказывает о базовом дескрипторе файла (тот, который он получает внутренне через системный вызов open при вызове fopen), недействительный, а не указатель файла, который вы передаете на fclose.