En C, y a-t-il une garantie avec du code avant un comportement indéfini?

Dans le code suivant, est-il garanti que “0 \ n” soit imprimé?

#include  int main(void) { int c = 0; printf("%d\n",c); printf("%d,%d\n",++c,++c); } 

Plus généralement, si un programme a un comportement indéfini, l’ensemble du programme devient-il indéfini ou uniquement à partir du sharepoint séquence qui commence le code problématique?

Remarque: je ne demande pas ce que le compilateur fait avec le second printf. Je demande si le premier printf est garanti pour se produire.

Je sais qu’un comportement indéfini est capable de faire exploser votre ordinateur, de planter votre programme ou autre chose.

Même en ignorant des choses comme “Tout ce qui pourrait arriver! Le programme pourrait remonter le temps et s’empêcher de fonctionner en premier lieu!”, Il est parfaitement possible pour un compilateur de détecter certaines formes de comportement indéfini et de ne pas comstackr dans ce cas où cas, vous ne l’auriez pas eu à courir en premier lieu. Alors oui, un comportement indéfini est en principe contagieux, sinon nécessairement dans la pratique la plupart du temps.

Tout ce qui a été fait par le programme avant qu’il ne provoque un comportement indéfini est bien sûr déjà fait.

Ainsi, printf() aurait envoyé le “0 \ n” au stream stdout . Le fait que ces données soient réellement transmises au périphérique dépend du fait que ce stream soit non tamponné, mis en mémoire tampon ou mis en mémoire tampon.

Là encore, je suppose qu’il est possible qu’un comportement non défini exécuté après les actions terminées et bien définies puisse causer des dommages dans la mesure où il apparaît que le comportement bien défini ne s’est pas terminé correctement. Je suppose que c’est un peu comme “si un arbre tombe dans les bois …”.


Mise à jour pour tenir compte de la conviction selon laquelle un comportement non défini à l’avenir signifie que tous les paris sont désactivés avant même que le programme ne commence à s’exécuter …

Voici ce que dit la norme C99 sur la modification de la valeur d’un object plusieurs fois entre des points de séquence:

Entre le sharepoint séquence précédent et le suivant, la valeur stockée d’un object doit être modifiée au plus une fois par l’évaluation d’une expression.

Et la norme dit aussi ceci à propos de l’access à un object:

access

   to read or modify the value of an object NOTE 1 Where only one of these two actions is meant, ``read'' or ``modify'' is used. NOTE 2 "Modify'' includes the case where the new value being stored is the same as the previous value. NOTE 3 Expressions that are not evaluated do not access objects. 

Je ne pense pas que modifier un object plus d’une fois entre des points de séquence soit un “comportement indéfini” au moment de la traduction, car les objects ne sont pas consultés / modifiés au moment de la traduction.

Néanmoins, je conviens qu’un compilateur qui diagnostique ce comportement indéfini au moment de la compilation serait une bonne chose, mais je pense aussi que cette question est plus intéressante si elle ne s’applique qu’aux programmes qui ont été compilés avec succès. Alors changeons un peu la question pour donner une situation dans laquelle le compilateur ne peut pas diagnostiquer un comportement indéfini au moment de la traduction:

 #include  #include  int main(int argc, char* argv[]) { int c[] = { 0, 1, 2, 3 }; int *p1 = &c[0]; int *p2 = &c[1]; if (argc > 1) { p1 = &c[atoi(argv[1])]; } if (argc > 2) { p2 = &c[atoi(argv[2])]; } printf("before: %d, %d\n", *p1, *p2); printf("after: %d, %d\n", ++(*p1),++(*p2)); /* possible undefined behavior */ return 0; } 

Dans ce programme, le comportement indéfini ne peut même pas exister au moment de la traduction – il ne se produit que si l’entrée du programme indique que le même élément de tableau doit être traité (ou qu’un type de comportement indéfini différent peut se produire si l’entrée spécifie valeurs d’index invalides).

Posons donc la même question avec ce programme: que dit la norme sur ce qui pourrait arriver aux premiers résultats ou effets secondaires de printf() ?

Si les entrées fournissent des valeurs d’index valides, le comportement indéfini ne peut se produire qu’après le premier printf() . Supposons que l’entrée soit argv[1] == "1" et argv[2] == "1" : l’implémentation du compilateur n’a pas la liberté de déterminer, avant le premier printf() que le comportement indéfini se produisant à un moment donné dans le programme est autorisé à ignorer la première printf() et à passer directement à son comportement indéfini de formatage du disque dur (ou à tout autre événement horrible qui pourrait se produire).

Étant donné que le compilateur accepte de traduire un programme, la promesse d’un futur comportement indéfini ne lui donne pas la liberté de faire ce qu’il veut avant que ce comportement indéfini ne se produise réellement. Bien sûr, comme je l’ai déjà mentionné, les dommages causés par le comportement indéfini pourraient éventuellement détruire les résultats précédents – mais ces résultats devaient avoir été obtenus.

Le comportement indéfini appartient au fournisseur du compilateur / hasard. Cela signifie qu’il pourrait créer une exception, corrompre des données dans votre programme, écraser votre collection de mp3, appeler un ange ou allumer le feu de votre grand-mère. Une fois que vous avez un comportement indéfini, votre programme entier devient indéfini.

Certains compilateurs et certaines configurations de compilateur fourniront des modes qui vous jetteront un os, mais une fois que vous aurez activé les optimisations, la plupart des programmes se comporteront plutôt mal.

Si un programme a un comportement indéfini, l’ensemble du programme devient-il indéfini ou uniquement à partir du sharepoint séquence qui commence le code problématique?

Le code allant jusqu’au point indéfini aura probablement fait le bon choix. Mais cela ne fait que du bien. Une fois que vous avez rencontré un comportement indéfini, tout peut littéralement arriver. Si quelque chose va arriver est couvert par la loi de Murphy 🙂

Les optimisations reposent sur un comportement bien défini et jouent à toutes sortes de trucs en dehors de cette boîte pour gagner en rapidité. Cela signifie que votre code peut s’exécuter complètement dans le désordre, à condition que les effets secondaires ne puissent pas être distingués pour un programme bien défini. Le simple fait qu’un comportement indéfini semble commencer à un moment donné de votre code source ne garantit pas que les lignes de code précédentes seront immunisées. Lorsque les optimisations sont activées, votre code peut très facilement atteindre le comportement non défini beaucoup plus tôt.

Matière à reflection: les exploits de dépassement de la mémoire tampon, tels qu’ils sont mis en œuvre par divers types de logiciels malveillants, reposent largement sur un comportement indéfini.

Pour un comportement indéfini, il convient probablement de faire la distinction entre les éléments détectables au moment de la compilation (comme dans votre cas) et les éléments dépendants des données qui ne se produisent qu’au moment de l’exécution, comme par exemple l’écriture accidentelle sur un object qualifié de type const .

Pour les applications ultérieures, le programme doit être exécuté jusqu’à ce que le fichier UB se produise, car il ne peut généralement pas le détecter à l’avance (la vérification des modèles est une tâche ardue pour les programmes non sortingviaux). envoie de l’argent au vendeur du compilateur ou alors 😉

Un choix plus raisonnable serait de ne rien produire, c’est de jeter une erreur et de ne pas comstackr du tout. Certains compilateurs le font quand on leur dit, par exemple avec gcc vous obtenez ceci avec -Wall -std=c99 --pedantic -Werror .