Longjmp
Ten artykuł od 2022-03 wymaga zweryfikowania podanych informacji. |
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.