Voici un simple morceau de code où une division par zéro se produit. J'essaye de l'attraper:
#include <iostream>
int main(int argc, char *argv[]) {
int Dividend = 10;
int Divisor = 0;
try {
std::cout << Dividend / Divisor;
} catch(...) {
std::cout << "Error.";
}
return 0;
}
Mais l'application se bloque quand même (même si je mets l'option -fexceptions
de MinGW ).
Est-il possible d'attraper une telle exception (qui, je crois, n'est pas une exception C++, mais une exception FPU)?
Je suis conscient que je pourrais vérifier le diviseur avant divisant, mais j'ai fait l'hypothèse que, parce qu'une division par zéro est rare (au moins dans mon application), il serait plus efficace d'essayer diviser (et attraper l'erreur si elle se produit) que de tester chaque fois le diviseur avant de diviser.
Je fais ces tests sur un ordinateur WindowsXP, mais j'aimerais le faire multiplateforme.
Ce n'est pas une exception. Il s'agit d'un erreur qui est déterminé au niveau matériel et est renvoyé au système d'exploitation, qui en informe ensuite votre programme d'une manière spécifique au système d'exploitation (comme, en tuant le processus).
Je crois que dans ce cas, ce qui se passe n'est pas une exception mais un signal. Si c'est le cas: le système d'exploitation interrompt le flux de contrôle principal de votre programme et appelle un gestionnaire de signaux qui, à son tour, met fin au fonctionnement de votre programme.
C'est le même type d'erreur qui apparaît lorsque vous déréférencer un pointeur nul (puis votre programme se bloque par signal SIGSEGV, défaut de segmentation).
Vous pouvez essayer d'utiliser les fonctions de <csignal>
en-tête pour essayer de fournir un gestionnaire personnalisé pour le signal SIGFPE (c'est pour les exceptions à virgule flottante, mais il se peut que ce soit aussi élevé pour la division entière par zéro - je ne suis vraiment pas sûr ici). Vous devez cependant noter que la gestion du signal dépend du système d'exploitation et que MinGW "émule" en quelque sorte les signaux POSIX dans l'environnement Windows.
Voici le test sur MinGW 4.5, Windows 7:
#include <csignal>
#include <iostream>
using namespace std;
void handler(int a) {
cout << "Signal " << a << " here!" << endl;
}
int main() {
signal(SIGFPE, handler);
int a = 1/0;
}
Production:
Signal 8 ici!
Et juste après avoir exécuté le gestionnaire de signaux, le système tue le processus et affiche un message d'erreur.
En utilisant cela, vous pouvez fermer toutes les ressources ou enregistrer une erreur après une division par zéro ou une déréférence de pointeur nul ... mais contrairement aux exceptions, ce n'est PAS un moyen de contrôler le flux de votre programme même dans des cas exceptionnels. A un programme valide ne devrait pas faire cela. La capture de ces signaux n'est utile qu'à des fins de débogage/diagnostic.
(Il y a quelques signaux utiles qui sont très utiles en général dans la programmation de bas niveau et qui n'entraînent pas la mort de votre programme juste après le gestionnaire, mais c'est un sujet profond).
La division par zéro est une erreur logique, un bug du programmeur. Vous ne devez pas essayer d'y faire face, vous devez le déboguer et l'éliminer. De plus, la capture d'exceptions est extrêmement coûteuse, bien plus que la vérification des diviseurs.
Vous pouvez utiliser la gestion d'exception structurée pour rattraper l'erreur de division par zéro. La façon dont cela est réalisé dépend de votre compilateur. MSVC offre une fonction pour intercepter les exceptions structurées en tant que catch(...)
et fournit également une fonction pour traduire les exceptions structurées en exceptions régulières, tout en offrant __try
/__except
/__finally
. Cependant, je ne connais pas suffisamment MinGW pour vous dire comment le faire dans ce compilateur.
Il n'y a pas de moyen standard pour intercepter la division par zéro du CPU.
N'optimisez pas prématurément une branche. Votre application est-elle vraiment liée au CPU dans ce contexte? J'en doute, et ce n'est pas vraiment une optimisation si vous cassez votre code. Sinon, je pourrais rendre votre code encore plus rapide:
int main(int argc, char *argv[]) { /* Fastest program ever! */ }
La division par zéro n'est pas une exception en C++, voir https://web.archive.org/web/20121227152410/http://www.jdl.co.uk/briefings/divByZeroInCpp.html
D'une certaine manière, la véritable explication manque toujours.
Est-il possible d'attraper une telle exception (qui, je crois, n'est pas une exception C++, mais une exception FPU)?
Oui, votre bloc catch
devrait fonctionner sur certains compilateurs. Mais le problème est que votre exception n'est pas une exception FP. Vous faites une division entière. Je ne sais pas si c'est aussi une erreur capturable mais ce n'est pas une exception FPU, qui utilise une fonctionnalité de la représentation IEEE des nombres à virgule flottante.
Sous Windows (avec Visual C++), essayez ceci:
BOOL SafeDiv(INT32 dividend, INT32 divisor, INT32 *pResult)
{
__try
{
*pResult = dividend / divisor;
}
__except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
return FALSE;
}
return TRUE;
}
MSDN: http://msdn.Microsoft.com/en-us/library/ms681409 (v = vs.85) .aspx
Pour éviter l'infini "Signal 8 ici!" messages, ajoutez simplement 'exit' au code Kos Nice:
#include <csignal>
#include <iostream>
#include <cstdlib> // exit
using namespace std;
void handler(int a) {
cout << "Signal " << a << " here!" << endl;
exit(1);
}
int main() {
signal(SIGFPE, handler);
int a = 1/0;
}
Eh bien, s'il y avait une gestion des exceptions à ce sujet, un composant en fait nécessaire la vérification. Vous ne perdez donc rien si vous le vérifiez vous-même. Et il n'y a pas grand-chose de plus rapide qu'une simple instruction de comparaison (une seule instruction CPU "sauter si égal à zéro" ou quelque chose comme ça, ne me souviens pas du nom)