Try Catch en C

Muhammad Husnain 12 octobre 2023
  1. Try-Catch en C
  2. Ajouter Finally à Try-Catch en C
Try Catch en C

Les mécanismes Try-Catch sont courants dans de nombreux langages de programmation tels que Python, C++ et JavaScript. La structure générale est ci-dessous.

try {
  /*
  Insert some lines of code that will probably give you errors
  */
} catch {
  /*
  Write some code to handle the errors you're getting.
  */
}

Ils vous permettent d’écrire du code sans avoir à tester chaque instruction. Si le programme exécuté dans le bloc try atteint une exception, l’exception est transmise au bloc catch.

Si l’exception correspond à un type d’exception, le code à l’intérieur du bloc catch est exécuté. Sinon, l’exception est renvoyée au bloc try.

Try-Catch en C

C ne prend pas en charge la gestion des exceptions. Au moins, il n’a aucun mécanisme intégré pour cela.

Ce guide démontrera une solution possible pour fournir la fonctionnalité try-catch en C. Il convient de noter que la solution n’est pas nécessairement complète.

Les systèmes de gestion des exceptions ne sont pas complets et sûrs sans un mécanisme pour libérer de la mémoire lorsque la pile a été parcourue, et C n’a pas de ramasse-miettes. Nous aurions aussi probablement besoin d’inclure des gestionnaires de contexte pour libérer de la mémoire.

Cette solution n’a pas vocation à fournir un mécanisme de try-catch complet et extensif. Ce concept peut techniquement être utilisé pour gérer quelques exceptions.

Nous allons construire la solution progressivement, en mettant à jour le code. Nous allons utiliser deux fonctions fournies par C, longjmp et setjmp, qui peuvent être obtenues à partir du fichier d’en-tête setjmp.h.

Nous allons examiner de plus près les définitions des deux fonctions.

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

setjmp prend une variable de type jmp_buf. Lorsque cette fonction est appelée directement, elle renvoie 0.

longjmp prend deux variables, et lorsque longjmp est invoqué avec la même variable jmp_buf, la fonction setjmp retourne avec la même valeur que le second argument de longjmp (val).

La variable env est ici essentiellement l’environnement d’appel, représentant l’état des registres et la position dans le code lorsque l’appel de la fonction est effectué. Lorsque longjmp est appelé, l’état de l’environnement appelant est copié dans le processeur et la valeur stockée dans l’argument val de longjmp est renvoyée.

Pour un simple bloc Try-Catch, l’idée est de mapper l’instruction Try sur l’instruction if et l’instruction Catch sera alors le else pour le conditionnel. C’est là que nous pouvons utiliser intelligemment le fait que setjmp peut renvoyer des valeurs différentes.

Si la fonction renvoie 0, alors nous savons que le seul morceau de code qui s’est exécuté était le code de notre bloc TRY. Si la fonction renvoie autre chose, nous devons entrer dans notre bloc CATCH avec le même état que lorsque nous avons commencé.

On peut appeler la fonction longjmp quand on THROW une exception.

Comme vous le verrez dans le code ci-dessous, nous devons également fermer le bloc TRY. Nous créons une fonction ENDTRY qui fournit la partie de fermeture du bloc do-while.

Cela nous aide également à créer plusieurs instructions TRY dans le même bloc. A noter qu’elles ne peuvent pas être imbriquées, car nous allons réutiliser la variable buf_state.

Un exemple de cette implémentation est ci-dessous.

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

#define TRY            \
  do {                 \
    jmp_buf buf_state; \
    if (!setjmp(buf_state)) {
#define CATCH \
  }           \
  else {
#define ENDTRY \
  }            \
  }            \
  while (0)
#define THROW longjmp(buf_state, 1)

int main() {
  TRY {
    printf("Testing Try statement \n");
    THROW;
    printf(
        "Statement should not appear, as the THROW block has already thrown "
        "the exception \n");
  }
  CATCH { printf("Got Exception \n"); }
  ENDTRY;

  return 0;
}

Production:

Testing Try statement
Got Exception

Pour les systèmes pratiques, cela ne suffit pas. Nous avons besoin de différents types d’exceptions.

L’exemple ci-dessus ne prend en charge qu’un seul type d’exception. Encore une fois, on peut utiliser les différentes valeurs de retour de setjmp.

Au lieu d’utiliser if-else, nous allons inverser cela avec un switch-case.

La conception est la suivante : l’instruction TRY utilisera une instruction switch, et CATCH sera une macro avec un paramètre représentant le type d’exception. Une condition de chaque instruction CATCH sera qu’elle doit fermer le case précédent à l’aide d’un break.

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

#define TRY                      \
  do {                           \
    jmp_buf buf_state;           \
    switch (setjmp(buf_state)) { \
      case 0:
#define CATCH(x) \
  break;         \
  case x:
#define ENDTRY \
  }            \
  }            \
  while (0)
#define THROW(x) longjmp(buf_state, x)

#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)

int main() {
  TRY {
    printf("Inside Try statement \n");
    THROW(EXCEPTION2);
    printf("This does not appear as exception has already been called \n");
  }
  CATCH(EXCEPTION1) { printf("Exception 1 called \n"); }
  CATCH(EXCEPTION2) { printf("Exception 2 called \n"); }
  CATCH(EXCEPTION3) { printf("Exception 3 called \n"); }
  ENDTRY;

  return 0;
}

Production:

Inside Try statement
Exception 2 called

Ajouter Finally à Try-Catch en C

Nous devons ajouter un bloc FINALLY pour une implémentation fonctionnelle complète de Try-Catch. Le bloc finally s’exécute généralement après la fin des blocs try et catch.

Il s’exécute indépendamment du fait qu’une exception soit levée ou non.

Comment pourrions-nous mettre cela en œuvre ? L’idée clé est d’utiliser le cas default du cas switch pour implémenter le bloc FINALLY.

Cependant, le switch-case n’exécuterait pas le cas default si une exception a été appelée dans un cas normal.

Nous allons utiliser un mécanisme similaire à Duff’s Device. Nous allons essentiellement entrelacer une instruction switch-case avec une instruction do-while.

La logique est quelque chose comme ça.

switch (an expression) {
  case 0:
    while (1) {
      // code for case 0
      break;
      case 1:
        // code for case 1
        break;
    }
  default:
    // code for default case
}

Nous utilisons l’une des fonctionnalités les plus controversées de C : les commutateurs ne se cassent pas automatiquement avant chaque étiquette de boîtier. Ici, l’instruction while est imbriquée dans le switch-case lorsqu’un break est appelé ; il sort de la boucle while et continue à parcourir les cas.

Dans le contexte de notre code (comme indiqué ci-dessous), les cas switch sont tous nos exceptions, et le cas default est dans notre bloc FINALLY. Naturellement, il tombera dans le cas default, car le code d’exception aurait déjà été exécuté.

Ceci est illustré dans le code ci-dessous.

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

#define TRY                      \
  do {                           \
    jmp_buf buf_state;           \
    switch (setjmp(buf_state)) { \
      case 0:                    \
        while (1) {
#define CATCH(x) \
  break;         \
  case x:
#define ENDTRY \
  }            \
  }            \
  while (0)
#define THROW(x) longjmp(buf_state, x)
#define FINALLY \
  break;        \
  }             \
  default:
#define EXCEPTION1 (1)
#define EXCEPTION2 (2)
#define EXCEPTION3 (3)

int main() {
  TRY {
    printf("Inside Try statement \n");
    THROW(EXCEPTION2);
    printf("This does not appear as exception has already been called \n");
  }
  CATCH(EXCEPTION1) { printf("Exception 1 called \n"); }
  CATCH(EXCEPTION2) { printf("Exception 2 called \n"); }
  CATCH(EXCEPTION3) { printf("Exception 3 called \n"); }
  FINALLY { printf("This will always be called! \n"); }
  ENDTRY;

  return 0;
}

Production:

Inside Try statement
Exception 2 called
This will always be called!

Ceci conclut le guide pour créer un système try-catch en C. Bien sûr, il y a des problèmes de mémoire possibles ici et quelques limitations (comme un manque de support pour les systèmes try-catch imbriqués), mais c’est un implémentation fonctionnelle try-catch en C.

Muhammad Husnain avatar Muhammad Husnain avatar

Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.

LinkedIn