Les changements de système d'exploitation, les modifications inoffensives du système (une version différente du matériel !) ou les changements de compilateur peuvent tous faire en sorte que l'UB, qui fonctionnait auparavant, ne fonctionne plus.
Mais c'est pire que ça.
Parfois, une modification apportée à une unité de compilation non apparentée, ou à un code éloigné dans la même unité de compilation, peut faire en sorte que l'UB qui fonctionnait auparavant ne fonctionne plus ; par exemple, deux fonctions ou méthodes en ligne avec des définitions différentes mais la même signature. L'une d'entre elles est silencieusement rejetée lors de l'édition de liens, et des modifications de code totalement inoffensives peuvent changer celle qui est rejetée.
Le code qui fonctionne dans un contexte donné peut soudainement cesser de fonctionner dans le même compilateur, le même système d'exploitation et le même matériel lorsque vous l'utilisez dans un contexte différent. Un exemple de ceci est la violation de l'aliasing fort ; le code compilé peut fonctionner lorsqu'il est appelé à l'endroit A, mais lorsqu'il est inlined (éventuellement au moment du lien !) le code peut changer de signification.
Votre code, s'il fait partie d'un projet plus vaste, pourrait appeler conditionnellement du code tiers (disons, une extension shell qui prévisualise un type d'image dans une boîte de dialogue d'ouverture de fichier) qui change l'état de certains drapeaux (précision en virgule flottante, locale, drapeaux de dépassement d'entier, comportement de division par zéro, etc.) Votre code, qui fonctionnait bien auparavant, présente maintenant un comportement complètement différent.
Ensuite, de nombreux types de comportements indéfinis sont intrinsèquement non déterministes. Accéder au contenu d'un pointeur après qu'il ait été libéré (même y écrire) peut être sûr à 99/100, mais à 1/100 la page a été échangée, ou quelque chose d'autre a été écrit avant que vous ne l'atteigniez. Vous avez alors une corruption de la mémoire. Elle passe tous vos tests, mais vous n'aviez pas une connaissance complète de ce qui pouvait se passer.
En utilisant un comportement indéfini, vous vous engagez à comprendre parfaitement la norme C++, tout ce que votre compilateur peut faire dans cette situation et toutes les réactions possibles de l'environnement d'exécution. Vous devez vérifier l'assemblage produit, et non la source C++, éventuellement pour l'ensemble du programme, à chaque fois que vous le construisez ! Vous engagez également toute personne qui lit ce code, ou qui modifie ce code, à ce niveau de connaissance.
Cela en vaut parfois encore la peine.
Délégués les plus rapides possibles utilise l'UB et sa connaissance des conventions d'appel pour être un non propriétaire très rapide. std::function
-comme le type.
Des délégués incroyablement rapides est en compétition. Il est plus rapide dans certaines situations, plus lent dans d'autres, et il est conforme à la norme C++.
L'utilisation de l'UB pourrait en valoir la peine, pour l'augmentation des performances. Il est rare que vous gagniez autre chose que les performances (vitesse ou utilisation de la mémoire) en utilisant l'UB.
Un autre exemple que j'ai vu est celui où nous avons dû enregistrer un callback avec une pauvre API C qui ne prenait qu'un pointeur de fonction. Nous créions une fonction (compilée sans optimisation), la copiait sur une autre page, modifiait une constante de pointeur dans cette fonction, puis marquait cette page comme exécutable, ce qui nous permettait de passer secrètement un pointeur avec le pointeur de fonction au callback.
Une autre solution consisterait à disposer d'un ensemble de fonctions de taille fixe (10 ? 100 ? 1000 ? 1 million ?) qui rechercheraient toutes un fichier std::function
dans un tableau global et l'invoquer. Cela limite le nombre de callbacks que nous pouvons installer à un moment donné, mais en pratique, c'est suffisant.