112 votes

L'utilisation de assert() en C++ est-elle une mauvaise pratique ?

J'ai tendance à ajouter beaucoup d'assertions à mon code C++ pour faciliter le débogage sans affecter les performances des versions. Maintenant, assert est une macro en C pur, conçue sans tenir compte des mécanismes du C++.

Le C++, quant à lui, définit std::logic_error Cette erreur est destinée à être lancée dans les cas où il y a une erreur dans la logique du programme (d'où son nom). Lancer une instance pourrait bien être l'alternative parfaite, plus proche de C++, à assert .

Le problème est que assert y abort Les deux terminent le programme immédiatement sans appeler les destructeurs, évitant ainsi le nettoyage, alors que lancer une exception manuellement ajoute des coûts d'exécution inutiles. Une façon de contourner ce problème serait de créer une macro d'assertion propre SAFE_ASSERT qui fonctionne exactement comme son homologue en C, mais qui lève une exception en cas d'échec.

Je peux penser à trois opinions sur ce problème :

  • S'en tenir à l'affirmation de C. Comme le programme est terminé immédiatement, il importe peu que les modifications soient correctement déroulées. De plus, l'utilisation de #define en C++ est tout aussi mauvais.
  • Lancer une exception et l'attraper dans main() . Permettre au code de sauter les destructeurs dans n'importe quel état du programme est une mauvaise pratique et doit être évité à tout prix, tout comme les appels à terminate(). Si des exceptions sont levées, elles doivent être attrapées.
  • Lancez une exception et laissez-la terminer le programme. Une exception mettant fin à un programme est acceptable, et en raison de NDEBUG ce qui ne se produira jamais dans une version finale. La capture n'est pas nécessaire et expose les détails de l'implémentation du code interne à des tiers. main() .

Existe-t-il une réponse définitive à ce problème ? Une référence professionnelle ?

Édité : Sauter les destructeurs n'est, bien sûr, pas un comportement indéfini.

110voto

Kerrek SB Points 194696
  • Les assertions servent à Débogage . L'utilisateur de votre code expédié ne devrait jamais les voir. Si une assertion est touchée, votre code doit être corrigé.

    CWE-617 : assertion d'accessibilité.

Le produit contient une instruction assert() ou une instruction similaire qui peut être déclenchée par un attaquant, ce qui entraîne une sortie de l'application ou un autre comportement qui est plus sévère que nécessaire.

Alors que l'assertion est bonne pour attraper les erreurs de logique et réduire le nombre de les chances d'atteindre des conditions de vulnérabilité plus sérieuses, elle peut conduire à un déni de service.

Par exemple, si un serveur gère plusieurs connexions simultanées, et qu'un assert() se produit dans une seule connexion qui provoque l'abandon de toutes les autres toutes les autres connexions, il s'agit d'une assertion atteignable qui conduit à un déni de service.

  • Les exceptions sont les suivantes circonstances exceptionnelles . Si l'un d'eux est rencontré, l'utilisateur ne pourra pas faire ce qu'il veut, mais il pourra peut-être reprendre ses activités ailleurs.

  • La gestion des erreurs est destinée au déroulement normal du programme. Par exemple, si vous demandez à l'utilisateur de saisir un nombre et que vous obtenez quelque chose qui ne peut pas être analysé, c'est normal parce que l'entrée de l'utilisateur n'est pas sous votre contrôle et que vous devez toujours traiter toutes les situations possibles comme une évidence. (Par exemple, bouclez jusqu'à ce que vous ayez une entrée valide, en disant "Désolé, essayez à nouveau" entre les deux).

88voto

bames53 Points 38303

Les assertions sont tout à fait appropriées dans le code C++. Les exceptions et autres mécanismes de gestion des erreurs ne sont pas vraiment destinés à la même chose que les assertions.

La gestion des erreurs est utilisée lorsqu'il est possible de récupérer ou de signaler une erreur à l'utilisateur. Par exemple, si une erreur survient lors de la lecture d'un fichier d'entrée, vous pouvez vouloir faire quelque chose à ce sujet. Les erreurs peuvent résulter de bogues, mais elles peuvent aussi être simplement la sortie appropriée pour une entrée donnée.

Les assertions servent à vérifier que les exigences d'une API sont satisfaites lorsque l'API ne serait normalement pas vérifiée, ou à vérifier des choses que le développeur pense avoir garanties par construction. Par exemple, si un algorithme nécessite une entrée triée, vous ne le vérifierez pas normalement, mais vous pourriez avoir une assertion pour le vérifier afin que les constructions de débogage signalent ce type de bogue. Une assertion devrait toujours indiquer un programme fonctionnant de manière incorrecte.


Si vous écrivez un programme où un arrêt non défini pourrait causer un problème, vous voudrez peut-être éviter les assertions. Un comportement indéfini au sens strict du langage C++ ne constitue pas un tel problème ici, puisque le fait de toucher une assertion est probablement déjà le résultat d'un comportement indéfini, ou la violation d'une autre exigence qui pourrait empêcher un nettoyage de fonctionner correctement.

De plus, si vous implémentez les assertions en termes d'exception, elles peuvent potentiellement être capturées et "gérées", même si cela contredit l'objectif même de l'assertion.

14voto

nogard Points 4351

Les assertions peuvent être utilisées pour vérifier les invariants internes de l'implémentation, comme l'état interne avant ou après l'exécution d'une méthode, etc. Si les assertions échouent, cela signifie que la logique du programme est cassée et que vous ne pouvez pas vous en remettre. Si l'assertion échoue, cela signifie que la logique du programme est rompue et qu'il est impossible de s'en remettre. Dans ce cas, le mieux que vous puissiez faire est de rompre dès que possible sans transmettre d'exception à l'utilisateur. Ce qui est vraiment bien avec les assertions (du moins sous Linux), c'est que le core dump est généré à la fin du processus et que vous pouvez donc facilement examiner la trace de la pile et les variables. Ceci est beaucoup plus utile pour comprendre l'échec logique que le message d'exception.

13voto

Jonathan Wakely Points 45593

Ne pas exécuter les destructeurs en raison de l'utilisation de abort() n'est pas un comportement indéfini !

Si c'était le cas, ce serait un comportement indéfini que d'appeler std::terminate() aussi, et donc quel serait l'intérêt de le fournir ?

assert() est tout aussi utile en C++ qu'en C. Les assertions ne servent pas à gérer les erreurs, elles servent à interrompre le programme immédiatement.

6voto

Ethan Gor Points 57

À mon avis, les assertions servent à vérifier des conditions qui, si elles sont violées, rendent tout le reste absurde. Et par conséquent, vous ne pouvez pas les récupérer ou plutôt, la récupération n'est pas pertinente.

Je les regrouperais en 2 catégories :

  • Les péchés du développeur (par exemple, une fonction de probabilité qui renvoie des valeurs négatives) :

float probability() { return -1.0 ; }

assert(probabilité() >= 0.0)

  • La machine est cassée (par exemple, la machine qui exécute votre programme est très mauvaise) :

int x = 1 ;

assert(x > 0) ;

Ce sont deux exemples triviaux mais pas trop éloignés de la réalité. Par exemple, pensez aux algorithmes naïfs qui renvoient des indices négatifs lorsqu'ils sont utilisés avec des vecteurs. Ou aux programmes intégrés dans du matériel personnalisé. Ou encore parce que des merdes arrivent .

Et s'il y a de telles erreurs de développement, vous ne devez pas avoir confiance dans les mécanismes de récupération ou de traitement des erreurs mis en œuvre. Il en va de même pour les erreurs matérielles.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X