Les exceptions sont-elles toujours indésirables dans l’environnement en temps réel?

Il y a quelques années, on m’a appris que, dans les applications en temps réel telles que les systèmes embarqués ou le développement C ++ (non-Linux) , les exceptions sont indésirables. (Peut-être que cette leçon était antérieure à gcc-2.95). Mais je sais aussi que la gestion des exceptions est devenue meilleure.

Donc, sont C ++ – Exceptions dans le contexte d’ applications en temps réel dans la pratique

  • totalement indésirable?
  • même être éteint via via le compilateur?
  • ou très soigneusement utilisable?
  • ou si bien géré maintenant, qu’on peut les utiliser presque librement, avec quelques idées en tête?
  • Est-ce que C ++ 11 change quelque chose avec cela?

Mise à jour : La gestion des exceptions nécessite-t- elle l’activation de RTTI (comme l’a suggéré un intervenant)? Y a-t-il des moulages dynamics ou similaires?

    Les exceptions sont maintenant bien gérées et les stratégies utilisées pour les implémenter les rendent en fait plus rapides que le test du code retour, car leur coût (en termes de vitesse) est pratiquement nul, tant que vous n’en lancez pas.

    Cependant, ils coûtent: en taille de code. Les exceptions fonctionnent généralement de pair avec RTTI, et malheureusement RTTI ne ressemble à aucune autre fonctionnalité C ++, dans la mesure où vous l’activez ou la désactivez pour l’ensemble du projet. Une fois activée, elle générera du code supplémentaire pour toute classe possédant une méthode virtuelle. , défiant ainsi le “vous ne payez pas pour ce que vous n’utilisez pas l’état d’esprit”.

    En outre, il nécessite un code supplémentaire pour sa gestion.

    Par conséquent, le coût des exceptions devrait être mesuré non pas en termes de rapidité, mais en termes de croissance du code.

    EDIT :

    De @Space_C0wb0y : Cet article de blog donne une petite vue d’ensemble et présente deux méthodes répandues de mise en œuvre des exceptions Jumps et Zero-Cost . Comme son nom l’indique, les bons compilateurs utilisent maintenant le mécanisme de coût zéro .

    L’article de Wikipedia sur la gestion des exceptions parle des deux mécanismes utilisés. Le mécanisme à coût zéro est le mécanisme piloté par table .

    EDIT :

    À partir de @Vlad Lazarenko dont le blog avait été mentionné plus haut, la présence d’une exception peut empêcher un compilateur d’inclure et d’optimiser du code dans les registres.

    Répondez juste à la mise à jour:

    La gestion des exceptions exige-t-elle vraiment que RTTI soit activé

    La gestion des exceptions nécessite en réalité quelque chose de plus puissant que la RTTI et la conversion dynamic à un niveau. Considérez le code suivant:

     try { some_function_in_another_TU(); } catch (const int &i) { } catch (const std::logic_error &e) {} 

    Ainsi, lorsque la fonction dans l’autre UT lève, elle va chercher la stack (soit vérifier tous les niveaux immédiatement, soit vérifier un niveau à la fois pendant le déroulement de la stack, c’est-à-dire jusqu’à l’implémentation) pour une clause catch correspondant à l’object être jeté.

    Pour effectuer cette correspondance, il n’est peut-être pas nécessaire que l’aspect de RTTI stocke le type dans chaque object, car le type d’une exception renvoyée est le type statique de l’expression de projection. Mais il faut comparer les types dans une instanceof manière, et cela doit être fait à l’exécution, car some_function_in_another_TU pourrait être appelé de n’importe où, avec n’importe quel type de capture sur la stack. À la différence de dynamic_cast , il doit effectuer cette instance d’exécution de vérification sur les types qui n’ont pas de fonctions membres virtuelles, et pour cela des types qui ne sont pas des types de classe. Cette dernière partie n’ajoute pas de difficulté, car les types non-classes n’ont pas de hiérarchie et il suffit donc de l’égalité de type, mais vous avez toujours besoin d’identificateurs de type pouvant être comparés à l’exécution.

    Donc, si vous activez des exceptions, vous avez besoin de la partie de RTTI qui fait des comparaisons de types, comme les comparaisons de types de dynamic_cast mais couvrant plus de types. Vous n’avez pas nécessairement besoin de la partie de RTTI qui stocke les données utilisées pour effectuer cette comparaison dans la vtable de chaque classe, où elle est accessible à partir de l’object – les données pourraient plutôt être codées au sharepoint chaque expression de lancement et de chaque clause catch . Mais je doute que ce soit une économie significative, puisque les objects de type typeid ne sont pas vraiment massifs, ils contiennent un nom souvent nécessaire dans une table de symboles, ainsi que des données définies par l’implémentation pour décrire la hiérarchie de types. Donc, vous pourriez aussi bien avoir tout RTTI d’ici là.

    Le problème des exceptions n’est pas nécessairement la vitesse (qui peut être très différente selon l’implémentation), mais c’est ce qu’elles font réellement.

    Dans le monde en temps réel, lorsque vous avez une contrainte de temps sur une opération, vous devez savoir exactement ce que fait votre code. Les exceptions fournissent des raccourcis qui peuvent influencer la durée d’exécution globale de votre code (le gestionnaire d’exceptions peut ne pas correspondre à la contrainte en temps réel ou en raison d’une exception, vous pouvez ne pas renvoyer la réponse à la requête, par exemple).

    Si vous voulez dire “temps réel” comme étant “incorporé”, la taille du code, comme mentionné, devient un problème. Le code incorporé peut ne pas nécessairement être en temps réel, mais il peut avoir des contraintes de taille (et le fait souvent).

    En outre, les systèmes intégrés sont souvent conçus pour fonctionner indéfiniment, dans une boucle d’événements infinie. L’exception peut vous emmener quelque part en dehors de cette boucle, et également corrompre votre mémoire et vos données (à cause du déroulement de la stack) – encore une fois, cela dépend de ce que vous en faites et de la manière dont le compilateur l’implémente.

    Donc, mieux vaut prévenir que guérir: n’utilisez pas les exceptions. Si vous pouvez supporter des défaillances occasionnelles du système, si vous exécutez une tâche autre que celle qui peut être facilement relancée, si vous n’êtes pas vraiment en temps réel, faites semblant d’être – alors vous pouvez probablement essayer. Si vous écrivez un logiciel pour un stimulant cardiaque, je préfère vérifier les codes de retour.

    Les exceptions C ++ ne sont toujours pas sockets en charge par chaque environnement en temps réel de manière à les rendre acceptables partout.

    Dans l’exemple particulier des jeux vidéo (qui ont une échéance de 16,6 ms pour chaque image), les principaux compilateurs implémentent les exceptions C ++ de telle manière que l’activation simple de la gestion des exceptions la ralentit considérablement et augmente la taille du code. de savoir si vous jetez des exceptions ou non. Étant donné que les performances et la mémoire sont essentielles sur une console de jeu, c’est un problème: les unités SPU de la PS3, par exemple, disposent de 256 Ko de mémoire pour le code et les données!

    En plus de cela, lancer des exceptions est encore assez lent (mesurez-le si vous ne me croyez pas) et peut provoquer des désallocations de tas qui sont également indésirables dans les cas où vous ne disposez pas de microsecondes.

    La seule exception à cette règle concerne les cas où l’exception peut être lancée une fois par exécution, pas une fois par image, mais littéralement une fois . Dans ce cas, la gestion structurée des exceptions est une méthode acceptable pour intercepter les données de stabilité du système d’exploitation lorsqu’un jeu se bloque et le renvoyer au développeur.

    L’implémentation du mécanisme d’exception est généralement très lente lorsqu’une exception est levée, sinon les coûts de leur utilisation sont presque nuls. A mon avis, les exceptions sont très utiles si vous les utilisez correctement.

    Dans les applications RT, les exceptions doivent être levées uniquement en cas de problème et le programme doit arrêter et corriger le problème (et éventuellement attendre l’interaction de l’utilisateur). Dans de telles circonstances, la résolution du problème prend plus de temps.

    Les exceptions fournissent un chemin caché pour signaler une erreur. Ils rendent le code plus court et plus lisible, facilitant ainsi la maintenance.

    Les implémentations classiques de la gestion des exceptions C ++ n’étaient toujours pas idéales et pourraient entraîner la quasi-totalité de l’implémentation du langage pour certaines cibles incorporées avec des ressources extrêmement limitées, même si le code utilisateur n’utilise pas explicitement ces fonctionnalités. Ceci est appelé “violation du principe de frais généraux zéro” par les documents récents du WG21, voir N4049 et N4234 pour plus de détails. Dans de tels environnements, la gestion des exceptions ne fonctionne pas comme prévu (en consommant des ressources système raisonnables), que l’application soit en temps réel ou non.

    Cependant, il devrait y avoir des applications en temps réel dans les environnements embarqués qui peuvent permettre ces frais généraux, par exemple un lecteur vidéo dans un appareil portable.

    La gestion des exceptions doit toujours être utilisée avec précaution. Lancer et intercepter des exceptions par image dans une application en temps réel pour toute plate-forme (pas seulement pour les environnements intégrés) est une mauvaise conception / implémentation et inacceptable en général.

    Il y a un autre inconvénient des exceptions.

    Les exceptions sont généralement faciles et bien gérées dans les langues avec une gestion automatique de la mémoire (comme C #, python, etc.)

    Mais en C ++, où la plupart du temps il faut contrôler l’allocation de mémoire et la désallocation d’objects (nouveaux et supprimer), dans de nombreuses situations, les exceptions devenaient très délicates. Lorsque des exceptions se produisent, il est souvent nécessaire de libérer les ressources allouées précédemment. Et dans certains cas, il est difficile de choisir le bon moment et la place qui lui convient. Et des choses comme les pointeurs automatiques pourraient vous sauver seulement dans certains cas.

    Les memory leaks, les erreurs de segmentation ou les comportements imprévus peuvent résulter de la manipulation incorrecte des objects / de la mémoire lors des exceptions lancées en C ++. Cela conduit à un développement plus lent et à un débogage de bogues vraiment délicats.

    Il y a généralement 3 ou 4 contraintes dans le développement embarqué / temps réel – surtout quand cela implique un développement en mode kernel

    • à différents moments – généralement lors de la gestion des exceptions matérielles – les opérations NE DOIVENT PAS lancer plus d’exceptions matérielles. Les structures de données implicites (vtables) et le code de c ++ (constructeurs et opérateurs par défaut et autres codes générés implicitement pour prendre en charge le mécanisme d’exception c ++) ne peuvent pas être placés dans la mémoire non paginée lorsqu’ils sont exécutés dans ce contexte.

    • Qualité du code – Le code c ++ en général peut cacher beaucoup de complexité dans les instructions qui paraissent sortingviales, ce qui rend le code difficile à vérifier visuellement pour les erreurs. les exceptions découplent la manipulation de la localisation, rendant difficile la couverture des tests par le code.

    • C ++ expose un modèle de mémoire très simple: les nouvelles atsortingbuent à partir d’un magasin gratuit infini, jusqu’à ce que vous soyez épuisé, et qu’il génère une exception. Dans les appareils à contrainte de mémoire, un code plus efficace peut être écrit, qui utilise explicitement des blocs de mémoire de taille fixe. Les allocations implicites de C + sur presque toutes les opérations rendent impossible l’audit de l’utilisation de la mémoire. De plus, la plupart des tas de c ++ présentent la propriété perturbasortingce qu’il n’ya pas de limite supérieure calculable pour une allocation de mémoire – ce qui rend difficile de prouver le temps de réponse des algorithmes sur les périphériques temps réel.