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.
Réponses
Trop de publicités?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
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++.
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.
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.
- Emboîtement des blocs try/catch. Utilisation d'une seule variable globale pour votre
jmp_buf
ne fonctionnera pas. - 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 ;)
- Réponses précédentes
- Plus de réponses