setjmp.h - setjmp.h

setjmp.h est un en- tête défini dans la bibliothèque standard C pour fournir des "sauts non locaux": un flux de contrôle qui s'écarte de la séquence habituelle d' appel et de retour du sous - programme . Les fonctions complémentaires setjmp et longjmp fournissent cette fonctionnalité.

Une utilisation typique de setjmp / longjmp est l'implémentation d'un mécanisme d'exception qui exploite la capacité de longjmp rétablir l'état du programme ou du thread, même à travers plusieurs niveaux d'appels de fonction. Une utilisation moins courante de setjmp consiste à créer une syntaxe similaire aux coroutines .

Fonctions des membres

int setjmp(jmp_buf env) Configure le jmp_buf tampon local et l'initialise pour le saut. Cette routine enregistre l'environnement d'appel du programme dans la mémoire tampon d'environnement spécifiée par l' env argument pour une utilisation ultérieure par longjmp . Si le retour provient d'un appel direct, setjmp renvoie 0. Si le retour provient d'un appel à longjmp , setjmp renvoie une valeur différente de zéro.
void longjmp(jmp_buf env, int value) Restaure le contexte du tampon d'environnement env qui a été sauvegardé par l'appel de la setjmp routine dans le même appel du programme. L'appel à longjmp partir d'un gestionnaire de signaux imbriqué n'est pas défini . La valeur spécifiée par value est transmise de longjmp à setjmp . Une fois longjmp terminé, l'exécution du programme se poursuit comme si l'invocation correspondante de setjmp venait juste de revenir. Si le value passé à longjmp est 0, setjmp se comportera comme s'il avait renvoyé 1; sinon, il se comportera comme s'il était revenu value .

setjmp enregistre l'environnement actuel (l'état du programme), à ​​un moment donné de l'exécution du programme, dans une structure de données spécifique à la plate-forme ( jmp_buf ) qui peut être utilisée à un moment ultérieur de l'exécution du programme par longjmp pour restaurer l'état du programme à celui enregistré par setjmp dans jmp_buf . Ce processus peut être imaginé comme un "saut" vers le point d'exécution du programme où a setjmp sauvé l'environnement. La valeur de retour (apparente) de setjmp indique si le contrôle a atteint ce point normalement (zéro) ou à partir d'un appel à longjmp (différent de zéro). Cela conduit à un commun idiome : . if( setjmp(x) ){/* handle longjmp(x) */}

POSIX .1 ne spécifie pas si setjmp , longjmp enregistre et restaure l'ensemble actuel de signaux bloqués ; si un programme utilise la gestion du signal, il doit utiliser sigsetjmp / de POSIX siglongjmp .

Types de membres

jmp_buf Un type de tableau, tel que struct __jmp_buf_tag[1] , adapté pour contenir les informations nécessaires pour restaurer un environnement d'appel.

Le raisonnement C99 décrit jmp_buf comme étant un type de tableau pour la compatibilité ascendante ; le code existant fait référence aux jmp_buf emplacements de stockage par nom (sans l' & opérateur address-of), ce qui n'est possible que pour les types de tableaux.

Mises en garde et limites

Lorsqu'un "goto non local" est exécuté via setjmp / longjmp en C ++ , le " déroulement de pile " normal ne se produit pas. Par conséquent, aucune action de nettoyage requise ne se produira non plus. Cela peut inclure la fermeture des descripteurs de fichier , le vidage des tampons ou la libération de la mémoire allouée au tas .

Si la fonction dans laquelle a setjmp été appelée retourne, il n'est plus possible de l'utiliser en toute sécurité longjmp avec l' jmp_buf objet correspondant . Cela est dû au fait que le cadre de pile est invalidé lorsque la fonction retourne. L'appel longjmp restaure le pointeur de pile , qui - parce que la fonction est retournée - pointerait vers un cadre de pile inexistant et potentiellement écrasé ou corrompu.

De même, C99 n'exige pas de longjmp conserver le cadre de pile actuel. Cela signifie que sauter dans une fonction qui a été quittée via un appel à longjmp n'est pas défini. Cependant, la plupart des implémentations de longjmp laissent le cadre de pile intact, permettant setjmp et longjmp d'être utilisé pour sauter dans les deux sens entre deux ou plusieurs fonctions - une fonctionnalité exploitée pour le multitâche .

Par rapport aux mécanismes des langages de programmation de plus haut niveau tels que Python , Java , C ++ , C # et même des langages pré-C tels que Algol 60 , la technique d'utilisation setjmp / longjmp pour implémenter un mécanisme d'exception est lourde. Ces langages fournissent des techniques de gestion des exceptions plus puissantes , tandis que des langages tels que Scheme , Smalltalk et Haskell fournissent des constructions de gestion de continuation encore plus générales .

Exemple d'utilisation

Exemple simple

L'exemple ci-dessous montre l'idée de base de setjmp. Là, main() appelle first() , qui à son tour appelle second() . Puis, second() saute de nouveau dans main() , sautant first() l'appel de 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;
}

Une fois exécuté, le programme ci-dessus affichera:

second
main

Notez que bien que le first() sous - programme soit appelé, " first " n'est jamais affiché. " main " est affiché lorsque l'instruction conditionnelle if (!setjmp(buf)) est exécutée une deuxième fois.

Gestion des exceptions

Dans cet exemple, setjmp est utilisé pour mettre entre parenthèses la gestion des exceptions, comme try dans certains autres langages. L'appel à longjmp est analogue à une throw instruction, permettant à une exception de renvoyer un état d'erreur directement au setjmp . Le code suivant respecte la norme ISO C 1999 et la spécification UNIX unique en appelant setjmp dans une gamme limitée de contextes:

  • Comme condition à un if , switch ou une déclaration d'itération
  • Comme ci-dessus en conjonction avec un simple ! ou une comparaison avec une constante entière
  • En tant qu'instruction (avec la valeur de retour inutilisée)

Le respect de ces règles peut faciliter la création de la mémoire tampon d'environnement par l'implémentation, ce qui peut être une opération sensible. Une utilisation plus générale de setjmp peut provoquer un comportement indéfini, tel que la corruption de variables locales; les compilateurs et les environnements conformes ne sont pas tenus de protéger ou même de mettre en garde contre une telle utilisation. Cependant, des expressions idiomatiques légèrement plus sophistiquées, telles que celles qui switch ((exception_type = setjmp(env))) { } sont courantes dans la littérature et la pratique, restent relativement portables. Une méthodologie de conformité simple est présentée ci-dessous, où une variable supplémentaire est conservée avec le tampon d'état. Cette variable pourrait être élaborée dans une structure incorporant le tampon lui-même.

Dans un exemple plus moderne, le bloc "try" habituel serait implémenté comme setjmp (avec du code de préparation pour les sauts à plusieurs niveaux, comme vu dans first ), le "throw" as longjmp avec le paramètre optionnel comme exception, et le "catch" comme le bloc "else" sous "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
}

La sortie de ce programme est:

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

Bien que la exception_type variable ne soit techniquement pas nécessaire ici puisque setjmp renvoie la valeur non nulle avec laquelle longjmp a été appelé (comme dans le deuxième et le premier), en pratique, un objet global plus élaboré serait utilisé pour accueillir des exceptions plus riches.

Dans le monde réel, setjmp-longjmp (sjlj) était le moyen standard de gestion des exceptions dans les compilateurs Windows C ++ tiers (à savoir MinGW ), car la gestion des exceptions structurée native est mal documentée en général et a également été brevetée sur 32- bit Windows jusqu'en 2014.

Multitâche coopératif

C99 prévoit qu'il longjmp est garanti de fonctionner uniquement lorsque la destination est une fonction appelante, c'est-à-dire que la portée de destination est garantie intacte. Passer à une fonction qui s'est déjà terminée par return ou longjmp n'est pas définie. Cependant, la plupart des implémentations de longjmp ne détruisent pas spécifiquement les variables locales lors de l'exécution du saut. Étant donné que le contexte survit jusqu'à ce que ses variables locales soient effacées, il pourrait en fait être restauré par setjmp . Dans de nombreux environnements (tels que Really Simple Threads et TinyTimbers ), des expressions idiomatiques telles que if(!setjmp(child_env)) longjmp(caller_env); peuvent permettre à une fonction appelée de faire une pause et de reprendre efficacement à un setjmp .

Ceci est exploité par les bibliothèques de threads pour fournir des installations multitâches coopératives sans utiliser setcontext ou d'autres installations de fibre . Alors que setcontext c'est un service de bibliothèque qui peut créer un contexte d'exécution dans la mémoire allouée par le tas et peut prendre en charge d'autres services tels que la protection contre le dépassement de la mémoire tampon , l'abus de setjmp est implémenté par le programmeur, qui peut réserver de la mémoire sur la pile et ne pas notifier la bibliothèque ou le fonctionnement système du nouveau contexte opérationnel. D'autre part, l'implémentation d'une bibliothèque de setcontext peut utiliser setjmp en interne d'une manière similaire à cet exemple pour enregistrer et restaurer un contexte, après qu'il a été initialisé d'une manière ou d'une autre.

Considérant que setjmp pour une fonction enfant fonctionnera généralement à moins d'être sabotée et setcontext , dans le cadre de POSIX , il n'est pas nécessaire de fournir des implémentations C, ce mécanisme peut être portable là où l' setcontext alternative échoue.

Étant donné qu'aucune exception ne sera générée lors du débordement de l'une des multiples piles dans un tel mécanisme, il est essentiel de surestimer l'espace requis pour chaque contexte, y compris celui contenant main() et incluant l'espace pour les gestionnaires de signaux qui pourraient interrompre l'exécution régulière. Le dépassement de l'espace alloué corrompra les autres contextes, généralement avec les fonctions les plus externes en premier. Malheureusement, les systèmes nécessitant ce type de stratégie de programmation sont souvent aussi petits et dotés de ressources limitées.

#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) */
}

Voir également

Les références

Liens externes