Justificación de las funciones de la biblioteca C que nunca establecen errno en cero

9

El estándar C exige que ninguna función de biblioteca estándar C se establezca errnoen cero. ¿Por qué es esto exactamente?

Podría entender que es útil para llamar a varias funciones y solo verificar errnodespués de la última, por ejemplo:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

Sin embargo, ¿esto no se considera "mala práctica"? Dado que sólo está comprobando errnoal final, sólo se sabe que una de las funciones hicieron fallar, pero no la que función ha fallado. ¿Simplemente saber que algo falló es lo suficientemente bueno para la mayoría de los programas prácticos?

Intenté buscar varios documentos de Fundamentos de C, pero muchos de ellos no tienen muchos detalles <errno.h>.

Drew McGowen
fuente
1
Supongo que es "no pagues por lo que no quieres". Si te importa errno, siempre puedes ponerlo a cero tú mismo.
Kerrek SB
2
Algo más a tener en cuenta es que una función puede establecerse errnoen un valor distinto de cero, incluso si tiene éxito. (Podría llamar a alguna otra función que falla, pero eso no constituye una falla en la función externa.)
Keith Thompson

Respuestas:

10

La biblioteca C no se establece errnoen 0 por razones históricas 1 . POSIX ya no afirma que sus bibliotecas no alterarán el valor en caso de éxito, y la nueva página de manual de Linuxerrno.h refleja esto:

El <errno.h>archivo de encabezado define la variable entera errno, que se establece mediante llamadas al sistema y algunas funciones de la biblioteca en caso de error para indicar qué salió mal. Su valor es significativo solo cuando el valor de retorno de la llamada indica un error (es decir, -1de la mayoría de las llamadas al sistema; -1o NULLde la mayoría de las funciones de la biblioteca); una función que tiene éxito se permitió al cambio errno.

La justificación ANSI C establece que el comité consideró que era más práctico adoptar y estandarizar la práctica existente de uso errno.

La maquinaria de informe de errores centrada en la configuración de errnogeneralmente se considera con tolerancia en el mejor de los casos. Requiere un `` acoplamiento patológico '' entre las funciones de la biblioteca y hace uso de una celda de memoria de escritura estática, que interfiere con la construcción de bibliotecas compartibles. Sin embargo, el Comité prefirió estandarizar esta maquinaria existente, aunque deficiente, en lugar de inventar algo más ambicioso.

Casi siempre hay una manera de verificar si hay errores fuera de verificar si errnose configuró. Comprobar si errnose configuró no siempre es confiable, ya que algunas llamadas requieren llamar a una API separada para obtener el motivo del error. Por ejemplo, ferror()se usa para buscar un error si obtiene un resultado corto de fread()o fwrite().

Curiosamente, su ejemplo de uso strtod()es uno de los casos en errnolos que se requiere establecer 0 antes de la llamada para detectar correctamente si se ha producido un error. Todas las strto*()funciones de cadena a número tienen este requisito, porque se devuelve un valor de retorno válido incluso ante un error.

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

El código anterior se basa en el comportamiento de strtod()lo documentado en Linux . El estándar C solo estipula que el subflujo no puede devolver un valor mayor que el positivo más pequeño double, y si la errnoconfiguración ERANGEestá definida o no, la implementación está definida 2 .

En realidad, hay una extensa redacción de aviso de certificación que recomienda establecer siempre errnoa 0 antes de una llamada a la biblioteca y verificar su valor después de que la llamada indica que se ha producido una falla . Esto se debe a que algunas llamadas de la biblioteca se establecerán errnoincluso si la llamada en sí fue exitosa 3 .

El valor de errnoes 0 al inicio del programa, pero ninguna función de biblioteca establece nunca a 0. Una errnollamada a la función de biblioteca puede establecer un valor distinto de cero si existe o no un error, siempre que el uso de errnono esté documentado en la descripción de la función en el Estándar C. Es significativo que un programa inspeccione el contenido errnosolo después de que se haya informado un error. Más precisamente, errnoes significativo solo después de que una función de biblioteca que se establece errnoen error ha devuelto un código de error.


1. Anteriormente dije que era para evitar enmascarar un error de una llamada anterior. No puedo encontrar ninguna evidencia para apoyar esta afirmación. También tuve un printf()ejemplo falso .
2. Gracias a @chux por señalar esto. La referencia es C.11 §7.22.1.3 ¶10.
3. Señalado por @KeithThompson en un comentario.

jxh
fuente
Problema menor: parece que el subflujo podría dar como resultado un resultado distinto de 0: "las funciones devuelven un valor cuya magnitud no es mayor que el número positivo normalizado más pequeño en el tipo de retorno" C11 7.22.1.3.10.
chux - Restablece a Mónica el
@chux: Gracias. Haré una edición. Dado que establecer errnoen la ERANGEimplementación se define en el caso de subflujo, en realidad no hay una forma portátil de detectar el subflujo. Mi código siguió lo que encontré en la página de manual de Linux en mi sistema.
jxh
Sus comentarios sobre las strto*funciones son exactamente la razón por la que pregunté si mi ejemplo se consideraría una mala práctica, pero es la única forma en que errnono se establecería en cero sería útil, o incluso aplicable.
@DrewMcGowen: Supongo que el único punto que puede tomar es que errnopuede establecerse incluso en caso de éxito, por lo que el solo hecho de que se haya configurado no es realmente un indicador suficientemente bueno de que se haya producido un error. Debe verificar los resultados de las llamadas individuales para saber si ocurrió un error.
jxh
1

Puede hacer una verificación de errores para ambas llamadas de función, si realmente le importa.

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

Como nunca sabemos cómo se llamó a la función mach en un proceso, ¿cómo puede la biblioteca establecer errores para cada llamada a la función?


fuente
1

Cambiar arbitrariamente el error no es análogo a 'atrapar y tragar' una excepción. Antes de que hubiera excepciones que se propagaran a través de diferentes capas de un programa y finalmente llegaran a un punto en el que la persona que llama capturaría y respondería a la excepción de alguna manera o simplemente pasaría el dinero, había errores. No alterar el error, a menos que de alguna manera esté manejando, anulando, tratando como irrelevante, el error, es esencial para este paradigma de propagación de manejo de errores de primera generación.

Andyz Smith
fuente