setjmp.h - setjmp.h

setjmp.h to nagłówek zdefiniowany w standardowej bibliotece C w celu zapewnienia „nielokalnych skoków”: przepływu sterowania, który różni się od zwykłego wywołania podprogramu i sekwencji powrotu. Funkcje uzupełniające setjmp i longjmp zapewniają tę funkcjonalność.

Typowym zastosowaniem setjmp / longjmp jest implementacja mechanizmu wyjątków, który wykorzystuje zdolność longjmp do ponownego ustanowienia programu lub stanu wątku, nawet na wielu poziomach wywołań funkcji. Mniej powszechnym zastosowaniem setjmp jest tworzenie składni podobnej do coroutines .

Funkcje składowe

int setjmp(jmp_buf env) Ustawia jmp_buf bufor lokalny i inicjuje go do skoku. Ta procedura zapisuje środowisko wywołujące programu w buforze środowiska określonym przez env argument do późniejszego użycia przez longjmp . Jeśli zwrot pochodzi z bezpośredniego wywołania, setjmp zwraca 0. Jeśli zwrot pochodzi z wywołania longjmp , setjmp zwraca wartość różną od zera.
void longjmp(jmp_buf env, int value) Przywraca kontekst buforu środowiska, env który został zapisany przez wywołanie setjmp procedury w tym samym wywołaniu programu. Wywołanie longjmp z zagnieżdżonej obsługi sygnału jest niezdefiniowane . Wartość określona przez value jest przekazywana z longjmp do setjmp . Po longjmp zakończeniu wykonywanie programu jest kontynuowane tak, jakby odpowiadające wywołanie funkcji setjmp właśnie zostało zwrócone. Jeśli value przekazana do longjmp wynosi 0, setjmp będzie zachowywać się tak, jakby zwróciła 1; w przeciwnym razie zachowa się tak, jakby powrócił value .

setjmp zapisuje bieżące środowisko (stan programu), w pewnym momencie wykonywania programu, w strukturze danych specyficznych dla platformy ( jmp_buf ), które mogą być wykorzystane w późniejszym momencie wykonywania programu przez longjmp przywrócenie stanu programu do objęcia setjmp w jmp_buf . Proces ten można sobie wyobrazić jako „skok” z powrotem do punktu wykonywania programu, w którym zostało setjmp zapisane środowisko. (Pozorna) wartość zwracana z setjmp wskazuje, czy sterowanie osiągnęło ten punkt normalnie (zero), czy z wywołania longjmp (niezerowe). Prowadzi to do wspólnej idiom : . if( setjmp(x) ){/* handle longjmp(x) */}

POSIX 0,1 nie precyzuje, czy setjmp i longjmp zapisywać i odtwarzać bieżący zestaw blokowanych sygnałów ; jeśli program wykorzystuje obsługę sygnałów, powinien używać sigsetjmp / siglongjmp .

Typy członków

jmp_buf Typ tablicy, na przykład struct __jmp_buf_tag[1] odpowiedni do przechowywania informacji potrzebnych do przywrócenia środowiska wywołującego.

W uzasadnieniu C99 opisano jmp_buf jako typ tablicowy zapewniający zgodność z poprzednimi wersjami ; istniejący kod odwołuje się do jmp_buf lokalizacji magazynowych według nazwy (bez & operatora address-of), co jest możliwe tylko w przypadku typów tablicowych.

Zastrzeżenia i ograniczenia

Kiedy „nielokalne goto” jest wykonywane przez setjmp / longjmp w C ++ , normalne „ rozwijanie stosu ” nie występuje. W związku z tym żadne wymagane działania czyszczące również nie zostaną wykonane. Może to obejmować zamykanie deskryptorów plików , opróżnianie buforów lub zwalnianie pamięci przydzielonej na sterty .

Jeśli funkcja, w której setjmp została wywołana, zwraca, nie można już bezpiecznie używać jej longjmp z odpowiednim jmp_buf obiektem. Dzieje się tak, ponieważ ramka stosu jest unieważniana, gdy funkcja zwraca. Wywołanie longjmp przywraca wskaźnik stosu , który - ponieważ funkcja zwróciła - wskazywałby na nieistniejącą i potencjalnie nadpisaną lub uszkodzoną ramkę stosu.

Podobnie C99 nie wymaga longjmp zachowania bieżącej ramki stosu. Oznacza to, że skok do funkcji, która została zakończona przez wywołanie, longjmp jest niezdefiniowany. Jednak w większości implementacji longjmp opuścić ramę stosu nienaruszony, co pozwala setjmp i longjmp być stosowane, aby przejść z powrotem i do przodu pomiędzy dwoma lub więcej funkcji-a funkcja wykorzystywana do wielozadaniowej .

W porównaniu z mechanizmami w językach programowania wyższego poziomu, takich jak Python , Java , C ++ , C # , a nawet w językach pre-C, takich jak Algol 60 , technika używania setjmp / longjmp do implementowania mechanizmu wyjątków jest uciążliwa. Języki te zapewniają potężniejsze techniki obsługi wyjątków , podczas gdy języki takie jak Scheme , Smalltalk i Haskell zapewniają jeszcze bardziej ogólne konstrukcje obsługi kontynuacji .

Przykładowe użycie

Prosty przykład

Poniższy przykład przedstawia podstawową ideę setjmp. Tam main() dzwoni first() , co z kolei dzwoni second() . Następnie second() wskakuje z powrotem do programu main() , pomijając first() wywołanie printf() .

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

static jmp_buf buf;

void second() {
    printf("second\n");         // prints
    longjmp(buf,1);             // jumps back to where setjmp was called - making setjmp now return 1
}

void first() {
    second();
    printf("first\n");          // does not print
}

int main() {   
    if (!setjmp(buf))
        first();                // when executed, setjmp returned 0
    else                        // when longjmp jumps back, setjmp returns 1
        printf("main\n");       // prints

    return 0;
}

Po uruchomieniu powyższy program wyświetli:

second
main

Zauważ, że chociaż first() wywoływana jest procedura, " first " nigdy nie jest drukowane. " main " jest drukowane, gdy instrukcja warunkowa if (!setjmp(buf)) jest wykonywana po raz drugi.

Obsługa wyjątków

W tym przykładzie setjmp jest używany do obsługi wyjątków w nawiasach, podobnie jak try w niektórych innych językach. Wywołanie longjmp jest analogiczne do throw instrukcji, umożliwiając wyjątkowi zwrócenie statusu błędu bezpośrednio do setjmp . Poniższy kod jest zgodny ze standardem ISO C 1999 i pojedynczą specyfikacją UNIX poprzez wywoływanie setjmp w ograniczonym zakresie kontekstów:

  • Jako warunek do if , switch lub oświadczenie iteracji
  • Jak wyżej, w połączeniu z pojedynczym ! lub porównaniem ze stałą całkowitą
  • Jako instrukcja (z niewykorzystaną wartością zwracaną)

Przestrzeganie tych reguł może ułatwić implementacji utworzenie bufora środowiska, co może być operacją wrażliwą. Bardziej ogólne użycie setjmp może spowodować niezdefiniowane zachowanie, takie jak uszkodzenie zmiennych lokalnych; zgodne kompilatory i środowiska nie są wymagane do ochrony lub nawet ostrzegania przed takim użyciem. Jednak nieco bardziej wyrafinowane idiomy, takie jak switch ((exception_type = setjmp(env))) { } powszechne w literaturze i praktyce, pozostają stosunkowo przenośne. Poniżej przedstawiono prostą metodologię dostosowywania, w której utrzymywana jest dodatkowa zmienna wraz z buforem stanu. Tę zmienną można by rozwinąć w strukturę obejmującą sam bufor.

W bardziej nowocześnie wyglądającym przykładzie, zwykły blok „try” zostałby zaimplementowany jako setjmp (z pewnym kodem przygotowującym do skoków wielopoziomowych, jak widać w first ), „throw” as longjmp z opcjonalnym parametrem jako wyjątkiem, a „catch” jako blok „else” w sekcji „try”.

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

static void first();
static void second();

/* Use a file scoped static variable for the exception stack so we can access
 * it anywhere within this translation unit. */
static jmp_buf exception_env;
static int exception_type;

int main(void) {
    char* volatile mem_buffer = NULL;

    if (setjmp(exception_env)) {
        // if we get here there was an exception
        printf("first failed, exception type: %d\n", exception_type);
    } else {
        // Run code that may signal failure via longjmp.
        puts("calling first");
        first();

        mem_buffer = malloc(300); // allocate a resource
        printf("%s\n", strcpy(mem_buffer, "first succeeded")); // not reached
    }

    free(mem_buffer); // NULL can be passed to free, no operation is performed

    return 0;
}

static void first() {
    jmp_buf my_env;

    puts("entering first"); // reached

    memcpy(my_env, exception_env, sizeof my_env);

    switch (setjmp(exception_env)) {
        case 3: // if we get here there was an exception.
            puts("second failed, exception type: 3; remapping to type 1");
            exception_type = 1;

        default: // fall through
            memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
            longjmp(exception_env, exception_type); // continue handling the exception

        case 0: // normal, desired operation
            puts("calling second"); // reached 
            second();
            puts("second succeeded"); // not reached
    }

    memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack

    puts("leaving first"); // never reached
}

static void second() {
    puts("entering second" ); // reached

    exception_type = 3;
    longjmp(exception_env, exception_type); // declare that the program has failed

    puts("leaving second"); // not reached
}

Wynik tego programu to:

calling first
entering first
calling second
entering second
second failed, exception type: 3; remapping to type 1
first failed, exception type: 1

Chociaż exception_type zmienna nie jest tu technicznie potrzebna, ponieważ setjmp zwraca wartość niezerową, z którą wywołano longjmp (tak jak w drugim i pierwszym), w praktyce do uwzględnienia bogatszych wyjątków użyty zostałby bardziej rozbudowany obiekt globalny.

W prawdziwym świecie setjmp-longjmp (sjlj) był standardowym sposobem obsługi wyjątków w kompilatorach C ++ innych firm (mianowicie MinGW ), ponieważ natywna obsługa wyjątków strukturalnych jest ogólnie słabo udokumentowana i została również opatentowana na 32- bit Windows do 2014 roku.

Współpraca wielozadaniowa

C99 zapewnia, że longjmp będzie działać tylko wtedy, gdy miejsce docelowe jest funkcją wywołującą, tj. Gwarantuje się, że zakres docelowy jest nienaruszony. Przechodzenie do funkcji, która została już zakończona przez return lub longjmp jest niezdefiniowana. Jednak większość implementacji longjmp specjalnie nie niszczy zmiennych lokalnych podczas wykonywania skoku. Ponieważ kontekst przetrwa, dopóki jego zmienne lokalne nie zostaną usunięte, w rzeczywistości może zostać przywrócony przez setjmp . W wielu środowiskach (takich jak Really Simple Threads i TinyTimbers ) idiomy, takie jak, if(!setjmp(child_env)) longjmp(caller_env); umożliwiają wywoływanej funkcji skuteczne wstrzymywanie i wznawianie działania w pliku setjmp .

Jest to wykorzystywane przez biblioteki wątków w celu zapewnienia możliwości współpracy wielozadaniowej bez korzystania z setcontext innych urządzeń światłowodowych . Podczas gdy setcontext jest to usługa biblioteczna, która może tworzyć kontekst wykonania w pamięci przydzielonej na sterty i może obsługiwać inne usługi, takie jak ochrona przed przepełnieniem bufora , nadużycie setjmp jest realizowane przez programistę, który może zarezerwować pamięć na stosie i nie powiadomić biblioteki lub operować system nowego kontekstu operacyjnego. Z drugiej strony implementacja biblioteki setcontext może używać wewnętrznie setjmp w sposób podobny do tego przykładu do zapisywania i przywracania kontekstu po jakimś zainicjowaniu.

Biorąc pod uwagę, że setjmp funkcja potomna będzie generalnie działać, o ile nie zostanie sabotowana, a setcontext jako część POSIX nie jest wymagana przez implementacje C, mechanizm ten może być przenośny, gdy setcontext alternatywa zawiedzie.

Ponieważ żaden wyjątek nie zostanie wygenerowany po przepełnieniu jednego z wielu stosów w takim mechanizmie, istotne jest, aby przeszacować przestrzeń wymaganą dla każdego kontekstu, w tym kontekstu zawierającego main() i obejmującego przestrzeń dla wszelkich programów obsługi sygnałów, które mogą zakłócać zwykłe wykonywanie. Przekroczenie przydzielonej przestrzeni spowoduje uszkodzenie innych kontekstów, zazwyczaj z pierwszymi funkcjami zewnętrznymi. Niestety, systemy wymagające takiej strategii programowania są często również małymi systemami z ograniczonymi zasobami.

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

jmp_buf mainTask, childTask;

void call_with_cushion();
void child();

int main() {
    if (!setjmp(mainTask)) {
        call_with_cushion(); // child never returns, yield
    } // execution resumes after this "}" after first time that child yields

    while (1) {
        printf("Parent\n");
        
        if (!setjmp(mainTask))
            longjmp(childTask, 1); // yield - note that this is undefined under C99
    }
}

void call_with_cushion() {
    char space[1000]; // Reserve enough space for main to run
    space[999] = 1; // Do not optimize array out of existence
    child();
}

void child() {
    while (1) {
        printf("Child loop begin\n");
        
        if (!setjmp(childTask))
            longjmp(mainTask, 1); // yield - invalidates childTask in C99

        printf("Child loop end\n");

        if (!setjmp(childTask))
            longjmp(mainTask, 1); // yield - invalidates childTask in C99
    }

    /* Don't return. Instead we should set a flag to indicate that main()
       should stop yielding to us and then longjmp(mainTask, 1) */
}

Zobacz też

Bibliografia

Zewnętrzne linki