Herramientas de usuario

Herramientas del sitio


setjmp

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 <setjmp.h>
  #include <stdio.h>

  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.

setjmp.txt · Última modificación: 2016/11/22 08:56 por lmateu