tipos - ¿Algún lenguaje bueno para el manejo de errores en programas de C directa?
tipos de errores lexicos en c (12)
Volviendo a algún trabajo de C.
Muchas de mis funciones se ven así:
int err = do_something(arg1, arg2, arg3, &result);
Con la intención, el resultado se llena con la función y el valor de retorno es el estado de la llamada.
El lado oscuro es que obtienes algo ingenuo como este:
int err = func1(...);
if (!err) {
err = func2(...);
if (!err) {
err = func3(...);
}
}
return err;
Podría macro lo supongo:
#define ERR(x) if (!err) { err = (x) }
int err = 0;
ERR(func1(...));
ERR(func2(...));
ERR(func3(...));
return err;
Pero eso solo funciona si estoy encadenando llamadas a funciones, mientras hago otro trabajo.
Obviamente, Java, C #, C ++ tienen excepciones que funcionan muy bien para este tipo de cosas.
Tengo curiosidad por saber qué hacen otras personas y cómo otras personas cometen errores en el manejo de sus programas de C en la actualidad.
¿Qué estás haciendo en las declaraciones de else
? Si nada, prueba esto:
int err = func1(...);
if (err) {
return err;
}
err = func2(...);
if (err) {
return err;
}
err = func3(...);
return err;
De esta manera, está cortocircuitando toda la función, sin molestarse en las siguientes llamadas de función.
EDITAR
Regresando y leyendo de nuevo, me doy cuenta de que no importa lo que haga en sus declaraciones de else
. Ese tipo de código puede ir fácilmente después de los bloques if
.
Algo que he visto recientemente es este idom:
int err;
do
{
err = func1 (...);
if (!err) break;
err = func2 (...);
if (!err) break;
err = func3 (...);
if (!err) break;
/* add more calls here */
} while (0);
if (err)
{
/* handle the error here */
return E_ERROR; /* or something else */
}
else
{
return E_SUCCESS;
}
Argumentos pro:
Evita el goto (abusa de la combinación while (0) / break para eso). Por qué querrías hacer esto? Mantiene baja la complejidad ciclomática y aún así pasará la mayoría de las comprobaciones del analizador de código estático (¿MISRA alguien?) Para los proyectos que se prueban contra la complejidad ciclomática, este es un dios enviado porque mantiene todas las cosas de inicialización juntas.
Contra argumentos:
El significado de la construcción de bucle do / while no es obvio porque una construcción de bucle se utiliza como un reemplazo de goto barato, y esto solo se puede ver en la cola del bucle. Estoy seguro de que, por primera vez, este constructo causará muchos "WTF" -momentos.
Al menos un comentario es necesario para explicar por qué el código está escrito de la forma en que se requiere.
Aquí hay un artículo bastante informativo y un archivo de prueba de la serie de artículos de IBM Unix:
Errores: errno en programas UNIX
Trabajando con el mecanismo de error estándar.
https://www.ibm.com/developerworks/aix/library/au-errnovariable/
Otro buen ejemplo de cómo implementar códigos de salida es el código fuente de curl (man 1 curl).
Debería ver lo que DirectX ha hecho con el HRESULT, es básicamente esto. Hay una razón por la que surgió la excepción. Alternativamente, si ejecuta en Win32, tienen SEH que se ejecuta en C programas.
Dos patrones típicos:
int major_func()
{
int err = 0;
if (err = minor_func1()) return err;
if (err = minor_func2()) return err;
if (err = minor_func3()) return err;
return 0;
}
int other_idea()
{
int err = minor_func1();
if (!err)
err = minor_func2();
if (!err)
err = minor_func3();
return err;
}
void main_func()
{
int err = major_func();
if (err)
{
show_err();
return;
}
happy_happy_joy_joy();
err = other_idea();
if (err)
{
show_err();
return;
}
happy_happy_joy_joy();
}
Otros han sugerido buenas ideas. Aquí están los modismos que he visto.
int err;
...
err = foo(...);
if (err)
return err;
...
Usted podría macro a algo como
#define dERR int err=0
#define CALL err =
#define CHECK do { if (err) return err } while(0)
...
void my_func(void) {
dERR;
...
CALL foo(...);
CHECK;
o, si te sientes realmente motivado, juega con CALL y CHECK para que puedan ser utilizados como
CALL foo(...) CHECK;
o
CALL( foo(...) );
-
A menudo, las funciones que necesitan una limpieza en la salida (por ejemplo, memoria libre) se escriben así:
int do_something_complicated(...) {
...
err = first_thing();
if (err)
goto err_out;
buffer = malloc(...);
if (buffer == NULL)
goto err_out
err = another_complicated(...);
if (err)
goto err_out_free;
...
err_out_free:
free(buffer);
err_out:
return err; /* err might be zero */
}
Podría usar ese patrón o intentar simplificarlo con macros.
-
Finalmente, si te sientes / realmente / motivado, puedes usar setjmp / longjmp.
int main(int argc, char *argv[]) {
jmp_buf on_error;
int err;
if (err = setjmp(on_error)) {
/* error occurred, error code in err */
return 1;
} else {
actual_code(..., on_error);
return 0;
}
}
void actual_code(..., jmp_buf on_error) {
...
if (err)
longjmp(on_error, err);
}
Esencialmente, una declaración de un nuevo jmp_buf y una función setjmp como configuración de un bloque try. El caso en el que setjmp devuelve un valor distinto de cero es su captura, y llamar a longjmp es su lanzamiento. Escribí esto con el paso del jmp_buf en caso de que desee manejadores anidados (por ejemplo, si necesita liberar cosas antes de señalar un error); Si no necesita eso, siéntase libre de declarar err y jmp_buf como globales.
Alternativamente, podrías usar macros para simplemente el argumento que se transmite. Sugeriría la forma en que la implementación de Perl lo hace:
#define pERR jmp_buf _err_handler
#define aERR _err_handler
#define HANDLE_ERRORS do { jmp_buf _err_handler; int err = setjmp(_err_handler);
#define END_HANDLE while(0)
#define TRY if (! err)
#define CATCH else
#define THROW(e) longjmp(_err_handler, e)
void always_fails(pERR, int other_arg) {
THROW(42);
}
void does_some_stuff(pERR) {
normal_call(aERR);
HANDLE_ERRORS
TRY {
always_fails(aERR, 23);
} CATCH {
/* err is 42 */
}
END_HANDLE;
}
int main(int argc, char *argv[]) {
HANDLE_ERRORS
TRY {
does_some_stuff(aERR);
return 0;
} CATCH {
return err;
}
DONE_ERRORS;
}
-
Uf. He terminado. (Ejemplos locos sin probar. Algunos detalles pueden estar desactivados.)
Puedes ponerte realmente tonto y hacer continuaciones:
void step_1(int a, int b, int c, void (*step_2)(int), void (*err)(void *) ) {
if (!c) {
err("c was 0");
} else {
int r = a + b/c;
step_2(r);
}
}
Probablemente esto no sea realmente lo que quiere hacer, pero es la cantidad de lenguajes de programación funcionales que se utilizan, y aún más a menudo cómo modelan su código para la optimización.
Si los códigos de error son booleanos, intente con el código más simple a continuación:
return func1() && func2() && func3()
Si tiene recursos que deben ser liberados al final, ¡a veces el antiguo y confiable goto
puede ser útil!
int
major_func(size_t len)
{
int err;
char *buf;
buf = malloc(len);
if (err = minor_func1(buf))
goto major_func_end;
if (err = minor_func2(buf))
goto major_func_end;
if (err = minor_func3(buf))
goto major_func_end;
major_func_end:
free(buf);
return err;
}
Siempre que esté trabajando con un contexto específico, creo que el siguiente patrón es muy bueno. La idea básica es que las operaciones en un estado de conjunto de errores no son opcionales, por lo que la comprobación de errores se puede posponer cuando sea conveniente.
Un ejemplo concreto: un contexto de deserialización. La decodificación de cualquier elemento puede fallar, pero la función puede continuar sin verificación de errores porque todas las funciones decode_*
no tienen decode_*
cuando el registro de serialización está en un estado de error. Es una cuestión de conveniencia u oportunidad u optimización para insertar decode_has_error
. En el siguiente ejemplo, no hay comprobación de errores, la persona que llama se encargará de eso.
void list_decode(struct serialization_record *rec,
struct list *list,
void *(*child_decode)(struct serialization_record *)) {
uint32_t length;
decode_begin(rec, TAG);
decode_uint32(rec, &length);
for (uint32_t i = 0; i < length; i++) {
list_append(list, child_decode(rec));
}
decode_end(rec, TAG);
}
Un enfoque que ha sido adoptado por OpenGL es no devolver los errores de las funciones en absoluto, sino presentar un estado de error que puede ser examinado después de la llamada a la función. Una cosa buena de este enfoque es que cuando tiene una función que realmente desea devolver algo que no sea un código de error, puede manejar los errores de la misma manera. Otra cosa interesante de esto es que si un usuario desea llamar a varias funciones y solo tiene éxito si todas tuvieron éxito, puede verificar si hay errores después de la cantidad de x llamadas.
/* call a number of functions which may error.. */
glMatrixMode(GL_MODELVIEW);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);
/* ...check for errors */
if ((error = glGetError()) != GL_NO_ERROR) {
if (error == GL_INVALID_VALUE)
printf("error: invalid value creating view");
else if (error == GL_INVALID_OPERATION)
printf("error: invalid operation creating view");
else if (error == GL_OUT_OF_MEMORY)
printf("error: out of memory creating view");
}
Y ahora para algo completamente diferente...
Otro enfoque es usar una estructura para contener su información de error, por ejemplo:
struct ErrorInfo
{
int errorCode;
char *errorMessage;
#if DEBUG
char *functionName;
int lineNumber;
#endif
}
La mejor manera de usar esto es devolver los resultados de su método como el código de retorno (por ejemplo, "FALSO por error", o "un puntero de archivo o NULL si falla", o "tamaño del búfer o 0 si falla", etc. ) y pase una ErrorInfo como parámetro que la función llamada cumplimentará si algo falla.
Esto proporciona un rico informe de errores: si el método falla, puede completar más de un simple código de error (por ejemplo, mensaje de error, línea de código y archivo de la falla, o lo que sea). Lo bueno de ser una estructura es que si piensas en algo, algo útil, más tarde, puedes agregarlo, por ejemplo, en mi estructura anterior he permitido que una compilación de depuración incluya la ubicación del error ( archivo / línea), pero podría agregar un volcado de toda la pila de llamadas allí en cualquier momento sin tener que cambiar el código del cliente.
Puede usar una función global para completar un ErrorInfo, de modo que la devolución del error se pueda administrar limpiamente, y puede actualizar la estructura para proporcionar más información fácilmente:
if (error)
{
Error(pErrorInfo, 123, "It failed");
return(FALSE);
}
... y puede tener variantes de esta función que devuelven FALSE, 0 o NULL, para permitir que la mayoría de las devoluciones de errores se expresen como una sola línea:
if (error)
return(ErrorNull(pErrorInfo, 123, "It failed"));
Esto le brinda muchas de las ventajas de una clase de Excepción en otros idiomas (aunque la persona que llama todavía necesita manejar los errores, las personas que llaman deben verificar los códigos de error y es posible que tengan que regresar antes de tiempo, pero no pueden hacer nada o seguir) nada y permitir que el error se propague una copia de seguridad de una cadena de métodos de llamada hasta que uno de ellos desee manejarlo, como una excepción.
Además, puede ir más lejos, para crear una cadena de informes de errores (como "InnerException"):
struct ErrorInfo
{
int errorCode;
char *errorMessage;
...
ErrorInfo *pInnerError; // Pointer to previous error that may have led to this one
}
Luego, si "captura" un error de una función a la que llama, puede crear una nueva descripción de error de nivel superior y devolver una cadena de estos errores. por ejemplo, "La velocidad del mouse volverá al valor predeterminado" (porque) "El bloque de preferencias ''MousePrefs'' no se pudo ubicar" (porque) "Error del lector XML" (porque) "Archivo no encontrado".
es decir
FILE *OpenFile(char *filename, ErrorInfo *pErrorInfo)
{
FILE *fp = fopen(filename, "rb");
if (fp == NULL)
return(ChainedErrorNull(pErrorInfo, "Couldn''t open file"));
return(fp);
}
XmlElement *ReadPreferenceXml(ErrorInfo *pErrorInfo)
{
if (OpenFile("prefs.xml", pErrorInfo) == NULL)
return(ChainedErrorNull(pErrorInfo, "Couldn''t read pref"));
...
}
char *ReadPreference(char *prefName, ErrorInfo *pErrorInfo)
{
XmlElement *pXml = ReadPreferenceXml(pErrorInfo);
if (pXml == NULL)
return(ChainedErrorNull(pErrorInfo, "Couldn''t read pref"));
...
}