2013-06-04 2 views
15

Согласно странице руководства fclose(3):Почему glclc fclose (NULL) вызывает ошибку сегментации вместо того, чтобы возвращать ошибку?

ВОЗВРАТ СТОИМОСТИ

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

ОШИБКИ

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); 
} 
+0

Возможный дубликат [fclose(), вызывающий ошибку сегментации] (http://stackoverflow.com/questions/1443164/fclose-causing-segmentation-fault) –

ответ

23

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

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

+0

Я не думаю, что возврат ошибки скрывает неопределенное поведение: это undefined - нет никаких сомнений в том, что это может привести к сбою -> если какой-либо результат скроет неопределенное поведение, тогда и любой другой результат также сделает это, включая сбои, которые скрывают, что вы не можете полагаться на получение сбоя. – Kaiserludi

+0

@ Kaiserludi: см. Https: // исходная программа.org/glibc/wiki/Style_and_Conventions # Invalid_pointers –

+0

Ваша ссылка не отвечает на вопрос, если возврат ошибки скрывает неопределенное поведение. В нем обсуждаются только причины, которые позволяют разработчикам gnu c решить не возвращать ошибку в случае недопустимых значений NULL. – Kaiserludi

4

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

Связанные вопрос: In either C or C++, should I check pointer parameters against NULL/nullptr?

Цитирую R.'s comment на одном из ответов на этот вопрос:

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

1

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

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

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

См https://stackoverflow.com/a/8442421/318716 взаимосвязанного обсуждения.

+1

Если« разумный »« скрывает полностью сломанный код, который демонстрирует неопределенное поведение », то я предполагаю, что это разумно , –

1

Я думаю Manpage переговоры о лежащих в основе дескриптора файла (тот, который получен ею внутренне через open системного вызова при вызове fopen) является недействительным, а не указателем файла который вы передаете fclose.

3

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

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

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

Можно повысить уровень ошибок при ошибках, чтобы повысить устойчивость к ошибкам программирования. Однако, если вы опасаетесь, что сбой программного обеспечения потеряет данные, обратите внимание, что точно так же может произойти, например. если ваше оборудование теряет мощность. Вот почему у нас есть автосохранение (since Unix text editors with two-letter names like ex and vi). Было бы предпочтительнее, чтобы ваше программное обеспечение выглядело кратковременно, а не продолжало противоречивое состояние.