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.
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