133 votes

Essayez les instructions catch en C

Je pensais aujourd'hui aux blocs try/catch existant dans d'autres langues. J'ai cherché sur Google pendant un certain temps, mais sans résultat. D'après ce que je sais, les try/catch n'existent pas en C. Cependant, existe-t-il un moyen de les "simuler" ?
Bien sûr, il existe des assertions et d'autres astuces, mais rien de comparable à try/catch, qui permettent également d'attraper l'exception soulevée. Je vous remercie.

115voto

JaredPar Points 333733

Le C en lui-même ne supporte pas les exceptions mais vous pouvez les simuler jusqu'à un certain point avec setjmp y longjmp appels.

static jmp_buf s_jumpBuffer;

void Example() { 
  if (setjmp(s_jumpBuffer)) {
    // The longjmp was executed and returned control here
    printf("Exception happened here\n");
  } else {
    // Normal code execution starts here
    Test();
  }
}

void Test() {
  // Rough equivalent of `throw`
  longjmp(s_jumpBuffer, 42);
}

Ce site web contient un bon tutoriel sur la façon de simuler les exceptions avec setjmp y longjmp

26voto

Alok Save Points 115848

Vous utilisez goto en C pour des situations similaires de gestion des erreurs.
C'est l'équivalent le plus proche des exceptions que vous pouvez obtenir en C.

23voto

Paul Hutchinson Points 402

Ok, je n'ai pas pu résister à l'envie de répondre à cette question. Permettez-moi d'abord de dire que je ne pense pas que ce soit une bonne idée de simuler ceci en C, car c'est vraiment un concept étranger au C.

Nous pouvons utiliser abuse du préprocesseur et des variables de pile locales pour donner une version limitée des try/throw/catch du C++.

Version 1 (lancements de portée locale)

#include <stdbool.h>

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) {__HadError=true;goto ExitJmp;}

La version 1 est un lancer local seulement (ne peut pas quitter la portée de la fonction). Elle repose sur la capacité de C99 à déclarer des variables dans le code (elle devrait fonctionner en C89 si le try est la première chose dans la fonction).

Cette fonction crée simplement une variable locale pour savoir s'il y a eu une erreur et utilise un goto pour passer au bloc de capture.

Par exemple :

#include <stdio.h>
#include <stdbool.h>

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) {__HadError=true;goto ExitJmp;}

int main(void)
{
    try
    {
        printf("One\n");
        throw();
        printf("Two\n");
    }
    catch(...)
    {
        printf("Error\n");
    }
    return 0;
}

Cela donne quelque chose comme :

int main(void)
{
    bool HadError=false;
    {
        printf("One\n");
        {
            HadError=true;
            goto ExitJmp;
        }
        printf("Two\n");
    }
ExitJmp:
    if(HadError)
    {
        printf("Error\n");
    }
    return 0;
}

Version 2 (saut de périmètre)

#include <stdbool.h>
#include <setjmp.h>

jmp_buf *g__ActiveBuf;

#define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else
#define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown)
#define throw(x) longjmp(*g__ActiveBuf,1);

La version 2 est beaucoup plus complexe mais fonctionne fondamentalement de la même manière. Elle utilise un long saut de la fonction courante vers le bloc try. Le bloc try utilise ensuite utilise un if/else pour sauter le bloc de code vers le bloc catch qui vérifie la variable locale pour voir si elle doit être attrapée.

L'exemple s'est encore élargi :

jmp_buf *g_ActiveBuf;

int main(void)
{
    jmp_buf LocalJmpBuff;
    jmp_buf *OldActiveBuf=g_ActiveBuf;
    bool WasThrown=false;
    g_ActiveBuf=&LocalJmpBuff;

    if(setjmp(LocalJmpBuff))
    {
        WasThrown=true;
    }
    else
    {
        printf("One\n");
        longjmp(*g_ActiveBuf,1);
        printf("Two\n");
    }
    g_ActiveBuf=OldActiveBuf;
    if(WasThrown)
    {
        printf("Error\n");
    }
    return 0;
}

Ceci utilise un pointeur global afin que longjmp() sache quel essai a été exécuté en dernier. Nous sommes en utilisant abuser de la pile pour que les fonctions enfant puissent aussi avoir un bloc try/catch.

L'utilisation de ce code présente un certain nombre d'inconvénients (mais constitue un exercice mental amusant) :

  • Il ne libérera pas la mémoire allouée car aucun déconstructeur n'est appelé.
  • Vous ne pouvez pas avoir plus d'un try/catch dans un scope (pas d'imbrication).
  • Vous ne pouvez pas réellement lancer des exceptions ou d'autres données comme en C++.
  • Pas du tout à l'abri des fils
  • Vous mettez les autres programmeurs en situation d'échec car ils ne remarqueront probablement pas le hack et essaieront de les utiliser comme des blocs try/catch en C++.

10voto

Kerrek SB Points 194696

En C99, vous pouvez utiliser setjmp / longjmp pour le flux de contrôle non local.

À l'intérieur d'une seule portée, le modèle de codage générique et structuré pour C en présence de multiples allocations de ressources et de multiples sorties utilise goto comme dans cet exemple . Cela ressemble à la façon dont le C++ implémente les appels au destructeur d'objets automatiques sous le capot, et si vous vous y tenez assidûment, cela devrait vous permettre un certain degré de propreté même dans les fonctions complexes.

6voto

Michael Anderson Points 21181

Alors que certaines des autres réponses ont couvert les cas simples en utilisant setjmp y longjmp Dans une application réelle, il y a deux préoccupations qui comptent vraiment.

  1. Emboîtement des blocs try/catch. Utilisation d'une seule variable globale pour votre jmp_buf ne fonctionnera pas.
  2. Threading. Une seule variable globale pour vous jmp_buf va causer toutes sortes de douleurs dans cette situation.

La solution à ces problèmes est de maintenir une pile de données de jmp_buf qui sont mis à jour au fur et à mesure. (Je pense que c'est ce que lua utilise en interne).

Donc, à la place de ceci (tiré de la réponse géniale de JaredPar)

static jmp_buf s_jumpBuffer;

void Example() { 
  if (setjmp(s_jumpBuffer)) {
    // The longjmp was executed and returned control here
    printf("Exception happened\n");
  } else {
    // Normal code execution starts here
    Test();
  }
}

void Test() {
  // Rough equivalent of `throw`
  longjump(s_jumpBuffer, 42);
}

Vous utiliseriez quelque chose comme :

#define MAX_EXCEPTION_DEPTH 10;
struct exception_state {
  jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
  int current_depth;
};

int try_point(struct exception_state * state) {
  if(current_depth==MAX_EXCEPTION_DEPTH) {
     abort();
  }
  int ok = setjmp(state->jumpBuffer[state->current_depth]);
  if(ok) {
    state->current_depth++;
  } else {
    //We've had an exception update the stack.
    state->current_depth--;
  }
  return ok;
}

void throw_exception(struct exception_state * state) {
  longjump(state->current_depth-1,1);
}

void catch_point(struct exception_state * state) {
    state->current_depth--;
}

void end_try_point(struct exception_state * state) {
    state->current_depth--;
}

__thread struct exception_state g_exception_state; 

void Example() { 
  if (try_point(&g_exception_state)) {
    catch_point(&g_exception_state);
    printf("Exception happened\n");
  } else {
    // Normal code execution starts here
    Test();
    end_try_point(&g_exception_state);
  }
}

void Test() {
  // Rough equivalent of `throw`
  throw_exception(g_exception_state);
}

Encore une fois, une version plus réaliste de ceci inclurait un moyen de stocker les informations d'erreur dans le fichier exception_state une meilleure gestion des MAX_EXCEPTION_DEPTH (peut-être en utilisant realloc pour faire grossir le tampon, ou quelque chose comme ça).

AVERTISSEMENT : Le code ci-dessus a été écrit sans aucun test. Il est purement destiné à vous donner une idée de la façon de structurer les choses. Des systèmes différents et des compilateurs différents devront implémenter le stockage local des threads différemment. Le code contient probablement des erreurs de compilation et des erreurs de logique - donc, bien que vous soyez libre de l'utiliser comme vous le souhaitez, TESTEZ le avant de l'utiliser ;)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X