Longjmp

Z Wikipedii, wolnej encyklopedii

setjmp/longjmp to mechanizm umożliwiający niskopoziomową kontrolę sterowania w języku C bez uciekania się do asemblera. Może być używany np. do implementacji wyjątków.

Użycie[edytuj | edytuj kod]

Należy wywołać funkcję setjmp z argumentem wskazującym bufor skoku. Funkcja wróci zwracając wartość 0. Następnie jeśli użyjemy funkcji longjmp podając jej jako argumenty bufor skoku i wartość do zwrócenia, sterowanie zostanie przekazane ponownie do miejsca powrotu odpowiadającej funkcji setjmp:

 jmp_buf jbuf;

 void foo()
 {
    if (setjmp(jbuf) != 0)
    {
        printf("world!\n");
    } else {
        printf("Hello, ");
        longjmp(jbuf, 1);
    }
}

W powyższym przykładzie wydrukowane zostanie Hello, world!\n.

Ponieważ często chcemy skakać pomiędzy funkcjami, bufor jest zwykle zmienną globalną. Typ jmp_buf jest typem wskaźnikowym, więc do funkcji setjmp i longjmp przekazujemy jbuf nie &jbuf.

Bufor skoku staje się nieaktualny po wyjściu z funkcji, z której wywołaliśmy setjmp.

Nagłówki[edytuj | edytuj kod]

 int setjmp(jmp_buf env);
 void longjmp (jmp_buf env, int val);

Funkcja jest opisana w standardzie POSIX.

Przykład[edytuj | edytuj kod]

W poniższym przykładzie użyjemy funkcji setjmp i longjmp do implementacji tablic, które zwracają wyjątek w przypadku próby dostępu poza granice tablicy.

 #include <setjmp.h>
 #include <stdio.h>
 #include <stdlib.h>

 jmp_buf array_access_exception;
 struct array *array_access_exception_base;
 int           array_access_exception_index;

 struct array
 {
    int *data;
    int size;
 };

 void alloc_array(struct array *array, int size)
 {
    array->data = malloc(sizeof(int) * size);
    array->size = size;
 }

 int read_array(struct array *array, int offset)
 {
    if (offset < 0 || offset >= array->size)
    {
        array_access_exception_base  = array;
        array_access_exception_index = offset;
        longjmp(array_access_exception, 1);
    }
    return array->data[offset];
 }

 void write_array(struct array *array, int offset, int value)
 {
    if (offset < 0 || offset >= array->size)
    {
        array_access_exception_base  = array;
        array_access_exception_index = offset;
        longjmp(array_access_exception, 2);
    }
    array->data[offset] = value;
 }

 void copy_array(struct array *a, struct array *b, int cnt)
 {
    int i;
    for(i=0; i<cnt; ++i)
        write_array(b, i, read_array(a, i));
 }

 int main()
 {
    struct array a,b;
    int asz, bsz, cnt;
    int xretval;

    if((xretval = setjmp(array_access_exception)) != 0)
    {
        fprintf(stderr, "Trying to %s at invalid offset %i of array %p\n",
                (xretval == 1) ? "read" : "write",
                array_access_exception_index,
                array_access_exception_base);
        return 1;
    }
    printf("Enter size of array a: ");
    scanf("%d", &asz);
    printf("Enter size of array b: ");
    scanf("%d", &bsz);
    printf("How many elements to copy: ");
    scanf("%d", &cnt);

    alloc_array(&a, asz);
    alloc_array(&b, bsz);
    copy_array(&a, &b, cnt);

    return 0;
 }

Próba użycia:

 $ ./arrayexcept
 Enter size of array a: 15
 Enter size of array b: 20
 How many elements to copy: 11

 $ ./arrayexcept
 Enter size of array a: 15
 Enter size of array b: 20
 How many elements to copy: 34
 Trying to read at invalid offset 15 of array 0xbffff350

array_access_exception to adres aktualnego handlera wyjątku. array_access_exception_base i array_access_exception_index przechowują dodatkowe informacje zwracane przez wyjątek.

Wszystkie funkcje korzystające z tablic, takie jak copy_array, są o wiele prostsze niż funkcje, które sprawdzałaby jawnie granice tablicy.