Considérer
void swap(int* a, int* b)
{
if (a != b){
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
}
int main()
{
int a = 0;
int b = 1;
swap(&a, &b); // after this b is 0 and a is 1
return a > b ? 0 : a / b;
}
swap
est une tentative de tromper le compilateur pour qu'il n'optimise pas le programme.
Le comportement de ce programme est-il défini? a / b
n'est jamais accessible, mais si c'était le cas, vous obtiendrez une division par zéro.
Il n’est pas nécessaire de baser votre position sur cette question sur l’utilité d’une construction ou d’une pratique de code donnée, ni sur tout ce qui est écrit sur C++, que ce soit dans sa réponse standard ou dans une autre réponse SO, quelle que soit la définition similaire de C++. être. La chose clé à considérer est C's définition du comportement indéfini :
comportement, lors de l'utilisation d'une construction de programme non portable ou erronée ou données erronées, pour lesquelles la présente Norme internationale n'impose pas. exigences
(C2011, 3.4.3/1; italiques ajoutés)
Ainsi, un comportement indéfini est déclenché temporellement ("lors de l'utilisation" d'une construction ou de données), pas par simple présence.* Il est commode que cela soit cohérent pour un comportement non défini découlant de données et celui résultant de constructions de programme; la norme n'a pas besoin d'être cohérente là-bas. Et comme le dit une autre réponse, cette définition «au moment de l’utilisation» est un bon choix de conception, car elle permet aux programmes d’éviter d’exécuter des comportements non définis associés à des données erronées.
D'un autre côté, si un programme fait exécute un comportement non défini, il découle de la définition de la norme que l'ensemble du comportement du programme est indéfini. Cette indétermination qui en découle est un type plus général découlant du fait que le fichier UB associé directement aux données ou conceptions erronées pourrait, en principe, inclure la modification du comportement d'autres parties du programme, même rétroactivement (ou apparemment). Il y a bien sûr des limitations extra-linguistiques sur ce qui pourrait arriver - donc non, les démons nasaux ne feront aucune apparition - mais celles-ci ne sont pas nécessairement aussi fortes qu'on pourrait le supposer.
* Avertissement: certaines constructions de programme sont utilisées au moment de la traduction. Celles-ci produisent UB en traduction de programme, avec pour résultat que chaque exécution du programme a un comportement totalement indéfini. Pour un exemple un peu stupide, si votre source de programme ne se termine pas par une nouvelle ligne non échappée, le comportement du programme est complètement indéfini (voir C2011, 5.1.1.2/1 , point 2).
Le comportement d'une expression non évaluée n'a aucune incidence sur le comportement d'un programme. Un comportement qui ne serait pas défini si l'expression était évaluée n'a aucune incidence sur le comportement du programme.
Si c'était le cas, alors ce code serait inutile:
if (p != NULL)
…; // Use pointer p.
(Vos XOR peuvent avoir un comportement indéfini, car ils peuvent produire une représentation d'interruption. Vous pouvez vaincre l'optimisation pour de tels exemples académiques en déclarant qu'un objet est volatil. Si un objet est volatil, l'implémentation C ne peut pas savoir si sa valeur peut changer avec le temps. aux moyens externes, de sorte que chaque utilisation de l'objet nécessite que l'implémentation en lise la valeur.)
En général, le code qui invoquerait le comportement non défini s'il est exécuté ne doit avoir aucun effet s'il n'est pas exécuté. Cependant, il existe quelques cas où des implémentations réelles peuvent se comporter de manière contraire et refuser de générer du code qui, sans être une violation de contrainte, ne peut éventuellement pas s'exécuter dans un comportement défini.
extern struct foo z;
int main(int argc, char **argv)
{
if (argc > 2) z;
return 0;
}
D'après mon interprétation de la norme, elle qualifie explicitement les conversions à valeurs multiples de types incomplets comme invoquant le comportement indéfini (entre autres choses, il est difficile de savoir ce qu'une implémentation pourrait générer du code pour une telle chose), de sorte que la norme n'imposerait aucune exigence au comportement si argc
est 3 ou plus. Cependant, je ne peux identifier dans la norme aucune contrainte que le code ci-dessus violerait, cependant, aucun comportement de motif ne doit pas être entièrement défini si argc
est égal à 2 ou moins. Néanmoins, de nombreux compilateurs, y compris gcc et clang, rejettent entièrement le code ci-dessus.