Permettez-moi de dire ceci clairement: nous n'invoquons pas de comportement indéfini dans nos programmes . Ce n'est jamais une bonne idée, point final. Il existe de rares exceptions à cette règle; par exemple, si vous êtes un implémenteur de bibliothèque implémentant offsetof . Si votre cas relève d'une telle exception, vous le savez probablement déjà. Dans ce cas, nous savons que l'utilisation de variables automatiques non initialisées est un comportement non défini .
Les compilateurs sont devenus très agressifs avec des optimisations concernant un comportement indéfini et nous pouvons trouver de nombreux cas où un comportement indéfini a conduit à des failles de sécurité. Le cas le plus tristement célèbre est probablement la suppression de la vérification du pointeur nul du noyau Linux que je mentionne dans ma réponse au bogue de compilation C ++? où une optimisation du compilateur autour d'un comportement non défini a transformé une boucle finie en boucle infinie.
Nous pouvons lire les Optimisations dangereuses et la perte de causalité du CERT ( vidéo ) qui disent, entre autres:
De plus en plus, les rédacteurs de compilateurs profitent de comportements non définis dans les langages de programmation C et C ++ pour améliorer les optimisations.
Fréquemment, ces optimisations interfèrent avec la capacité des développeurs à effectuer une analyse de cause à effet sur leur code source, c'est-à-dire analyser la dépendance des résultats en aval par rapport aux résultats antérieurs.
Par conséquent, ces optimisations éliminent la causalité dans les logiciels et augmentent la probabilité de pannes, de défauts et de vulnérabilités logicielles.
En ce qui concerne spécifiquement les valeurs indéterminées, le rapport de défaut standard C 451: Instabilité des variables automatiques non initialisées rend la lecture intéressante. Il n'a pas encore été résolu mais introduit le concept de valeurs bancales qui signifie que l'indétermination d'une valeur peut se propager à travers le programme et peut avoir différentes valeurs indéterminées à différents points du programme.
Je ne connais aucun exemple où cela se produit, mais à ce stade, nous ne pouvons pas l'exclure.
De vrais exemples, pas le résultat que vous attendez
Il est peu probable que vous obteniez des valeurs aléatoires. Un compilateur pourrait optimiser complètement la boucle. Par exemple, avec ce cas simplifié:
void updateEffect(int arr[20]){
for(int i=0;i<20;i++){
int r ;
arr[i] = r ;
}
}
clang l'optimise ( voir en direct ):
updateEffect(int*): # @updateEffect(int*)
retq
ou peut-être obtenir tous les zéros, comme avec ce cas modifié:
void updateEffect(int arr[20]){
for(int i=0;i<20;i++){
int r ;
arr[i] = r%255 ;
}
}
voir en direct :
updateEffect(int*): # @updateEffect(int*)
xorps %xmm0, %xmm0
movups %xmm0, 64(%rdi)
movups %xmm0, 48(%rdi)
movups %xmm0, 32(%rdi)
movups %xmm0, 16(%rdi)
movups %xmm0, (%rdi)
retq
Ces deux cas sont des formes parfaitement acceptables de comportement indéfini.
Remarque, si nous sommes sur un Itanium, nous pourrions nous retrouver avec une valeur de piège :
[...] si le registre contient une valeur spéciale non-chose, lire les pièges du registre à l'exception de quelques instructions [...]
Autres notes importantes
Il est intéressant de noter la variance entre gcc et clang notée dans le projet UB Canaries sur leur volonté de profiter d'un comportement indéfini par rapport à la mémoire non initialisée. L'article note ( soulignement le mien ):
Bien sûr, nous devons être parfaitement clairs avec nous-mêmes: une telle attente n'a rien à voir avec la norme de langage et tout à voir avec ce qu'un compilateur particulier arrive à faire, soit parce que les fournisseurs de ce compilateur ne veulent pas exploiter cette UB ou simplement parce qu'ils n'ont pas encore réussi à l'exploiter . Lorsqu'aucune garantie réelle du fournisseur de compilateur n'existe, nous aimons à dire que les UB non encore exploitées sont des bombes à retardement : elles attendent de se déclencher le mois prochain ou l'année prochaine lorsque le compilateur deviendra un peu plus agressif.
Comme Matthieu M. le souligne, ce que tout programmeur C devrait savoir sur le comportement indéfini # 2/3 est également pertinent pour cette question. Il dit entre autres (c'est moi qui souligne ):
La chose importante et effrayante à réaliser est que n'importe quelle
optimisation basée sur un comportement non défini peut commencer à être déclenchée à tout moment dans le code bogué . L'intégration, le déroulement des boucles, la promotion de la mémoire et d'autres optimisations continueront de s'améliorer, et une partie importante de leur raison d'être est d'exposer des optimisations secondaires comme celles ci-dessus.
Pour moi, c'est profondément insatisfaisant, en partie parce que le compilateur finit inévitablement par être blâmé, mais aussi parce que cela signifie que d'énormes corps de code C sont des mines terrestres qui ne demandent qu'à exploser.
Par souci d'exhaustivité, je devrais probablement mentionner que les implémentations peuvent choisir de rendre le comportement indéfini bien défini, par exemple, gcc autorise la punition de type via les unions tandis qu'en C ++, cela semble être un comportement indéfini . Si tel est le cas, l'implémentation doit le documenter et ce ne sera généralement pas portable.