===== setjmp/longjmp =====
C no posee excepciones como Java (o C++). Un efecto parecido se
puede lograr con setjmp y longjmp. La primera función marca
un lugar de retorno. La segunda función realiza un salto
de una función a otra función más abajo en la pila de llamadas
a funciones.
Por ejemplo, si se busca un dato específico en un árbol binario no ordenado, esta búsqueda es recursiva. Cuando encontramos el dato buscado no gustaría terminar de inmediato la búsqueda volviendo a la primera de las llamadas.
Sin excepciones esto no se puede hacer: hay que retornar de cada una de las llamadas recursivas. Para eso existe
en C setjmp y longjmp. Estas funciones permiten tomar un //atajo// para retornar directamente a un punto específico
de la ejecución.
Esta es la solución para el caso de una busqueda recursiva en un árbol binario no ordenado.
#include
#include
typedef struct node {
int x;
struct node *left, *right;
} Node;
void search2(Node *t, int y, jmp_buf env) {
if (t==NULL)
return;
if (t->x==y)
longjmp(env, 1); /* salto a la función search */
search2(t->left, y, env);
search2(t->right, y, env);
}
int search(Node *t, int y) {
jmp_buf env;
int found;
if (setjmp(env)==0) { /* marca el destino del salto con longjmp */
search2(t, y, env);
found= 0;
}
else {
found= 1;
}
return found;
}
Node n1= { 1, NULL, NULL };
Node n2= { 2, NULL, NULL };
Node r= { 3, &n1, &n2 };
int main(int argc, char **argv) {
printf("%d\n", search(&r, atoi(argv[1])));
return 0;
}
Es importante que la función destinataria del salto no haya retornado aún. De otra forma
los efectos son impredecibles. Observe que setjmp retorna 2 veces: el primer retorno ocurre
la primera vez que se invoca setjmp para marcar el destino del salto, el segundo es cuando
se invoca longjmp. El valor retornado en el primer retorno es siempre 0. En el segundo
retorno, el valor es lo que se especificó como argumento en el longjmp y por eso es importante
que sea distinto de 0 para poder distinguirlo del primer retorno.
==== Manejo de errores ====
La funciones setjmp y longjmp se usan sobretodo para manejar situaciones de error, al igual que las excepciones. Por ejemplo un programa puede tener un ciclo en donde se leen comandos de la entrada estándar y se procesan. Si el programa detecta un error al final de una larga cadena de llamadas de funciones, no es necesario manejar el error en todas las funciones. Se puede invocar longjmp para retornar directamente al ciclo de lectura de comandos. Esto sería algo así:
jmp_buf env;
int main() {
for(;;) {
if (setjmp(env)==0) {
char buf[80];
int leidos= fread(buf, 1, 80, stdin);
if (leidos==0)
break;
procesar(buf, leidos);
}
}
return 0;
}
void procesar(char *buf, int n) { ... f(); ... }
void f() { ... g(); ... }
void g() { ... h(); ... }
void h() {
...
if (x>=1000) {
fprintf(stderr, "el parámetro x=%d es demasiado grande\n", x);
longjmp(env, 1);
}
...
}
De esta forma el programa puede seguir procesando comandos en vez de terminar abruptamente el programa o alternativamente manejar la condición de error en g, h, procesar, etc.
El inconveniente de este enfoque es que si la función f pidió memoria o abrió un archivo que tenía que liberar o cerrar al retornar, si ocurre el error la memoria y el archivo abierto se transformarán en goteras. Esto en Java se puede resolver usando try/finally en f, cerrando por ejemplo el archivo en el bloque finally. Java asegura que esas instrucciones se ejecutarán en caso de una excepción. Lamentablemente C no posee algo parecido.
==== Discusión ====
Hay que reconocer que el funcionamiento de setjmp y longjmp es
de lo más retorcido del lenguaje C. De hecho estas funciones
tienen que programarse en assembler ya que su funcionalidad
no se puede expresar en C.
Uno de los principios de diseño de C es la minimización de las abstracciones del lenguaje. Si una
funcionalidad del lenguaje se puede lograr con funciones, entonces ella se realiza con funciones
de biblioteca, sin añadir una nueva abstracción al lenguaje. Esto explica la ausencia de la
instrucción try/catch/finally de Java en C. Por supuesto, esto es discutible desde el punto
de vista de la robustez, ya que el uso de setjmp y longjmp en C conducen a errores que son
difíciles de diagnosticar.