Est-ce que l'existence d'une telle déclaration dans un programme donné signifie que que tout le programme est indéfini ou que le comportement devient indéfini seulement que lorsque le flux de contrôle atteint cette instruction ?
Ni l'un ni l'autre. La première condition est trop forte et la seconde est trop faible.
Les accès aux objets sont parfois séquencés, mais la norme décrit le comportement du programme en dehors du temps. Danvil a déjà cité :
si une telle exécution contient une opération non définie, cette norme internationale n'impose aucune exigence à l'implémentation l'exécution de ce programme avec cette entrée (même pas en ce qui concerne les opérations précédant la première opération non définie)
Cela peut être interprété :
Si l'exécution du programme donne lieu à un comportement non défini, alors le programme entier a comportement indéfini.
Ainsi, une déclaration inaccessible avec UB ne donne pas au programme UB. Un énoncé atteignable qui (à cause des valeurs des entrées) n'est jamais atteint, ne donne pas le programme UB. C'est pourquoi votre première condition est trop forte.
Maintenant, le compilateur ne peut pas, en général, dire ce qu'est l'UB. Ainsi, pour permettre à l'optimiseur de réordonner des instructions avec un potentiel UB qui serait réordonnable si leur comportement était défini, il est nécessaire de permettre à l'UB de "remonter dans le temps" et de se tromper avant le point de séquence précédent (ou dans la terminologie C++11, pour que l'UB affecte les choses qui sont séquencées avant la chose UB). Par conséquent, votre deuxième condition est trop faible.
Un exemple majeur de ceci est lorsque l'optimiseur s'appuie sur un aliasing strict. L'intérêt des règles d'aliasing strict est de permettre au compilateur de réordonner des opérations qui ne pourraient pas être réordonnées valablement s'il était possible que les pointeurs en question aliasent la même mémoire. Ainsi, si vous utilisez des pointeurs à alias illégal, et que UB se produit, alors il peut facilement affecter une déclaration "avant" la déclaration UB. En ce qui concerne la machine abstraite, l'instruction UB n'a pas encore été exécutée. En ce qui concerne le code objet réel, il a été partiellement ou totalement exécuté. Mais la norme n'essaie pas d'entrer dans les détails de ce que cela signifie pour l'optimiseur de réordonner les instructions, ou quelles en sont les implications pour UB. Elle donne simplement à l'implémentation la permission de se tromper dès qu'elle le souhaite.
Vous pouvez penser à ça comme à "UB a une machine à remonter le temps".
Pour répondre spécifiquement à vos exemples :
- Le comportement est seulement indéfini si 3 est lu.
- Les compilateurs peuvent éliminer du code comme étant mort si un bloc de base contient une opération dont il est certain qu'elle est indéfinie. Ils sont autorisés (et je suppose qu'ils le font) dans les cas qui ne sont pas un bloc de base mais où toutes les branches mènent à UB. Cet exemple n'est pas un candidat à moins que
PrintToConsole(3)
est en quelque sorte connu pour être sûr de revenir. Il pourrait lancer une exception ou autre.
Un exemple similaire à votre deuxième est l'option gcc -fdelete-null-pointer-checks
qui peut prendre le code suivant (je n'ai pas vérifié cet exemple spécifique, je le considère comme illustratif de l'idée générale) :
void foo(int *p) {
if (p) *p = 3;
std::cout << *p << '\n';
}
et le changer en :
*p = 3;
std::cout << "3\n";
Pourquoi ? Parce que si p
est nul alors le code a UB de toute façon, donc le compilateur peut supposer qu'il n'est pas nul et l'optimiser en conséquence. Le noyau linux a trébuché sur ce point ( https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1897 ) essentiellement parce qu'il fonctionne dans un mode où le déréférencement d'un pointeur nul n'est pas est censé être UB, il est censé résulter en une exception matérielle définie que le noyau peut gérer. Lorsque l'optimisation est activée, gcc demande l'utilisation de -fno-delete-null-pointer-checks
afin de fournir cette garantie hors normes.
P.S. La réponse pratique à la question "quand le comportement indéfini frappe-t-il ?" est "10 minutes avant que vous ne prévoyiez de partir pour la journée".