¿Por qué fclose(NULL) de glibc causa un error de segmentación en lugar de devolver un error?
linux segmentation-fault (5)
Según la página del fclose(3)
:
VALOR DEVUELTO
Al finalizar con éxito se devuelve 0. De lo contrario, se devuelve
EOF
y la variable globalerrno
se establece para indicar el error. En cualquier caso, cualquier acceso adicional (incluida otra llamada afclose()
) a la secuencia da como resultado un comportamiento indefinido.ERRORES
EBADF
El descriptor de archivo subyacentefp
no es válido.La función
fclose()
también puede fallar y establecererrno
para cualquiera de los errores especificados para las rutinasclose(2)
,write(2)
offlush(3)
.
Por supuesto, fclose(NULL)
debería fallar, pero espero que vuelva con un errno
normalmente en lugar de morir por falta de segmentación directamente. ¿Hay alguna razón de este comportamiento?
Gracias por adelantado.
ACTUALIZACIÓN: pondré mi código aquí (estoy probando strerror()
, particularmente).
FILE *not_exist = NULL;
not_exist = fopen("nonexist", "r");
if(not_exist == NULL){
printError(errno);
}
if(fclose(not_exist) == EOF){
printError(errno);
}
Creo que la página de manual habla de un descriptor de archivo subyacente (el que se obtiene internamente a través de la llamada al sistema open
cuando llama a fopen
) es inválido, no el puntero del archivo que pasa al fclose
.
Este problema de fclose()
parece ser un legado de FreeBSD, y fue aceptado sin críticas por los campos de Microsoft y Linux.
Pero HP, SGI, Solaris y CYGWIN, por otro lado, todos manejan fclose(NULL)
razonable. Por ejemplo, man fclose
para man fclose
, que usa newlib en lugar de glibc de OP, afirma:
fclose returns 0 if successful (including when FP is NULL or not an open file)
Consulte https://.com/a/8442421/318716 para una discusión relacionada.
Los errores de los que habla la página de manual son errores de tiempo de ejecución, no errores de programación. No puedes pasar NULL
a ninguna API esperando un puntero y esperar que la API haga algo razonable. Pasar un puntero NULL
a una función documentada para requerir un puntero a los datos es un error.
Pregunta relacionada: En C o C ++, ¿debo verificar los parámetros del puntero con NULL / nullptr?
Para citar el comentario de R. sobre una de las respuestas a esa pregunta:
... parece que se están confundiendo los errores que surgen de condiciones excepcionales en el entorno operativo (fs lleno, sin memoria, sin conexión a la red, etc.) con errores de programación. En el primer caso, por supuesto, un programa robusto debe ser capaz de manejarlos con gracia. En este último, un programa robusto no puede experimentarlos en primer lugar.
fclose(NULL)
debería tener éxito. free(NULL)
tiene éxito, porque eso hace que sea más fácil escribir el código de limpieza.
Lamentablemente, no es así como se definió. Por lo tanto, no puede utilizar fclose(NULL)
en programas portátiles. (Por ejemplo, consulte http://pubs.opengroup.org/onlinepubs/9699919799/ ).
Como han mencionado otros, generalmente no desea una devolución de error si pasa NULL al lugar equivocado. Desea un mensaje de advertencia, al menos en las versiones debug / test. Dereferencing NULL te da un mensaje de advertencia inmediato, y la oportunidad de recopilar un backtrace que identifica el error de programación :). Mientras está programando, un error de seguridad es el mejor error que puede obtener. C tiene muchos más errores sutiles, que tardan mucho más en depurarse ...
Es posible abusar de las devoluciones de errores para aumentar la robustez contra errores de programación. Sin embargo, si le preocupa que una falla del software pierda datos, tenga en cuenta que puede suceder exactamente lo mismo, por ejemplo, si su hardware pierde energía. Es por eso que tenemos guardado automático ( desde editores de texto Unix con nombres de dos letras como ex y vi ). Seguiría siendo preferible que su software se bloquee visiblemente, en lugar de continuar con un estado inconsistente.
fclose
requiere como argumento un puntero de FILE
obtenido por fopen
, uno de los flujos estándar stdin
, stdout
o stderr
, o de alguna otra forma definida por la implementación. Un puntero nulo no es uno de estos, por lo que el comportamiento no está definido, como lo fclose((FILE *)0xdeadbeef)
. NULL no es especial en C; aparte del hecho de que está garantizado para comparar no igual a cualquier puntero válido, es como cualquier otro puntero no válido, y su uso invoca un comportamiento indefinido, excepto cuando la interfaz lo está pasando a los documentos como parte de su contrato que NULL
tiene algún significado especial para ello.
Además, volver con un error sería válido (ya que el comportamiento no está definido de todos modos) pero es un comportamiento dañino para una implementación, ya que oculta el comportamiento indefinido . El resultado preferible de invocar un comportamiento indefinido es siempre un bloqueo, porque resalta el error y le permite corregirlo. La mayoría de los usuarios de fclose
no comprueban un valor de retorno de error, y apostaría a que la mayoría de las personas lo suficientemente tontas para pasar NULL
a fclose
no serán lo suficientemente inteligentes como para verificar el valor de retorno de fclose
. Se podría argumentar que las personas deberían verificar el valor de retorno de fclose
en general, ya que la descarga final podría fallar, pero esto no es necesario para los archivos que se abren solo para leer, o si se llamó a fclose
manualmente antes de fclose
(que es un lenguaje inteligente de todos modos porque es más fácil manejar el error mientras todavía tienes el archivo abierto).