Je travaille avec un compilateur pour une puce DSP qui génère délibérément du code qui accède à un après la fin d'un tableau en code C qui ne le fait pas!
Cela est dû au fait que les boucles sont structurées de sorte que la fin d'une itération prélève certaines données pour l'itération suivante. Ainsi, la donnée extraite à la fin de la dernière itération n'est jamais réellement utilisée.
Écrire du code C comme ça invoque un comportement indéfini, mais ce n'est qu'une formalité d'un document de standards qui se préoccupe d'une portabilité maximale.
Plus souvent qu'autrement, un programme qui accède hors des limites n'est pas habilement optimisé. C'est tout simplement buggé. Le code récupère une certaine valeur de déchets et, contrairement aux boucles optimisées du compilateur susmentionné, le code utilise ensuite la valeur dans les calculs suivants, les corrompant ainsi.
Cela vaut la peine d'attraper des bogues comme ça, et il vaut donc la peine de rendre le comportement non défini, même pour cette seule raison: pour que l'exécution puisse produire un message de diagnostic comme "dépassement de la ligne à la ligne 42 de main.c".
Sur les systèmes avec mémoire virtuelle, il se peut qu'un tableau soit alloué de telle sorte que l'adresse qui suit se trouve dans une zone non mappée de mémoire virtuelle. L'accès bombardera alors le programme.
Soit dit en passant, notez qu'en C, nous sommes autorisés à créer un pointeur qui dépasse la fin d'un tableau. Et ce pointeur doit être plus grand que n'importe quel pointeur à l'intérieur d'un tableau. Cela signifie qu'une implémentation C ne peut pas placer un tableau juste à la fin de la mémoire, où l'adresse un plus serait bouclée et aurait l'air plus petite que les autres adresses du tableau.
Néanmoins, l'accès à des valeurs non initialisées ou hors limites est parfois une technique d'optimisation valide, même s'il n'est pas portable au maximum. C'est par exemple pourquoi l'outil Valgrind ne signale pas les accès aux données non initialisées lorsque ces accès se produisent, mais uniquement lorsque la valeur est utilisée ultérieurement d'une manière qui pourrait affecter le résultat du programme. Vous obtenez un diagnostic comme «branche conditionnelle dans xxx: nnn dépend de la valeur non initialisée» et il peut parfois être difficile de localiser son origine. Si tous ces accès étaient immédiatement bloqués, il y aurait beaucoup de faux positifs provenant du code optimisé par le compilateur ainsi que du code correctement optimisé à la main.
En parlant de cela, je travaillais avec un codec d'un fournisseur qui dégageait ces erreurs lorsqu'il était porté sur Linux et exécuté sous Valgrind. Mais le vendeur m'a convaincu que seulement plusieurs bitsde la valeur utilisée provenait en fait de la mémoire non initialisée, et ces bits ont été soigneusement évités par la logique. Seuls les bons bits de la valeur étaient utilisés et Valgrind n'a pas la possibilité de retrouver le bit individuel. Le matériel non initialisé provient de la lecture d'un mot après la fin d'un flux binaire de données codées, mais le code sait combien de bits se trouvent dans le flux et n'utilisera pas plus de bits qu'il n'y en a réellement. Étant donné que l'accès au-delà de la fin de la matrice de flux binaires ne cause aucun dommage à l'architecture DSP (il n'y a pas de mémoire virtuelle après la matrice, pas de ports mappés en mémoire et l'adresse ne se termine pas), c'est une technique d'optimisation valide.
"Comportement indéfini" ne signifie pas vraiment grand-chose, car selon ISO C, simplement inclure un en-tête qui n'est pas défini dans la norme C, ou appeler une fonction qui n'est pas définie dans le programme lui-même ou la norme C, sont des exemples d'indéfini comportement. Un comportement indéfini ne signifie pas "non défini par quiconque sur la planète" simplement "non défini par la norme ISO C". Mais bien sûr, un comportement parfois indéfini n'est vraiment absolument pas défini par quiconque.