Question

#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}
Était-ce utile?

La solution

C a le concept de comportement non défini, à savoir certaines constructions linguistiques sont syntaxiquement valide, mais vous ne pouvez pas prédire le comportement lorsque le code est exécuté.

Pour autant que je sache, la norme ne dit pas explicitement pourquoi existe le concept de comportement non défini. Dans mon esprit, il est tout simplement parce que les concepteurs de langage voulaient qu'il y ait une certaine marge de manœuvre dans la sémantique, au lieu de par exemple exiger que toutes les implémentations poignée débordement d'entier dans exactement la même manière, ce qui imposerait très probablement des coûts importants de performance, ils ont juste quitté le comportement non définie de sorte que si vous écrivez du code qui provoque le débordement d'entier, tout peut arriver.

Donc, avec cela à l'esprit, pourquoi ces « problèmes »? La langue dit clairement que certaines choses conduisent à un comportement non défini de. Il n'y a pas de problème, il n'y a pas « devrait » impliqué. Si le comportement indéfini change lorsque l'une des variables impliquées est déclarée volatile, cela ne prouve pas ou quoi que ce soit changer. Il est non défini ; vous ne pouvez pas raisonner sur le comportement.

Votre exemple le plus intéressant prospectifs, celui avec

u = (u++);

est un exemple livre de texte de comportement non défini (voir l'entrée de Wikipedia sur rel="noreferrer"> pointe).

Autres conseils

Il suffit de compiler et de démonter votre ligne de code, si vous êtes si enclin à savoir exactement comment il est vous obtenez ce que vous obtenez.

Voici ce que je reçois sur ma machine, avec ce que je pense qui se passe:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(je suppose que ... l'instruction 0x00000014 était une sorte d'optimisation du compilateur?)

Je pense que les parties pertinentes de la norme C99 sont 6,5 expressions, §2

  

Entre le point de séquence précédent et suivant un objet doit avoir sa valeur stockée   modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur avant   est en lecture seule pour déterminer la valeur à stocker.

et 6.5.16 Opérateurs d'affectation, §4:

  

L'ordre d'évaluation des opérandes est non spécifiée. Si une tentative de modifier   le résultat d'un opérateur d'affectation ou pour y accéder après le prochain point de la séquence, la   comportement est indéfini.

Le comportement ne peut pas vraiment expliquer, car il invoque à la fois comportement non spécifié et comportement non défini , donc nous ne pouvons pas faire des prédictions générales sur ce code, bien que si vous lisez Olve Maudal de des travaux tels que profonde C et projet de norme C99 paragraphe section6.5 3 dit ( Souligné par l'auteur ):

  

est indiqué le groupement d'opérateurs et d'opérandes par le syntax.74) Sauf indication   plus tard (pour la fonction d'appel (), &&, ||,: et les opérateurs par des virgules), l'ordre d'évaluation des sous-expressions et l'ordre dans lequel les effets secondaires se produisent sont à la fois non spécifiée <. / p>

Alors, quand nous avons une ligne comme ceci:

i = i++ + ++i;

nous ne savons pas si i++ ou ++i seront évalués en premier. Ceci est principalement pour donner le compilateur meilleures options d'optimisation.

Nous avons aussi un comportement non défini ici aussi puisque le programme est en train de modifier les variables (i, u, etc ..) plus d'une fois entre séquence pointe. De projet de paragraphe section standard 6.5 2 ( Souligné par l'auteur ):

  

Entre le point de séquence précédent et suivant un objet doit avoir sa valeur stockée   modifié au plus une fois par l'évaluation d'une expression. En outre, la valeur avant   est en lecture seule pour déterminer la valeur à stocker .

on cite les exemples de code suivants comme étant non défini:

i = ++i + 1;
a[i++] = i; 

Dans tous ces exemples, le code tente de modifier un objet de plus d'une fois dans le même point de la séquence, qui prendra fin avec l'; dans chacun de ces cas:

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

un comportement non spécifié est défini dans la section projet de norme C99 dans la section 3.4.4 comme:

  

utilisation d'une valeur non spécifiée, ou tout autre comportement où la présente Norme internationale fournit   deux ou plusieurs possibilités et impose aucune autre exigence sur laquelle est choisi dans une   par exemple

et un comportement non défini est défini à la section 3.4.3 comme:

  comportement

, lors de l'utilisation d'une construction de programme non portable ou erronées ou des données erronées,   pour lequel la présente Norme internationale impose aucune exigence

et note que:

  

comportement non défini possibles va de tenir compte de la situation complètement avec des résultats imprévisibles, à se comporter lors de la traduction ou de l'exécution du programme dans une caractéristique de manière documentée de l'environnement (avec ou sans l'émission d'un message de diagnostic), pour mettre fin à une translation ou à l'exécution ( à l'émission d'un message de diagnostic).

La plupart des réponses ici cité de la norme C soulignant que le comportement de ces constructions ne sont pas définis. Pour comprendre pourquoi le comportement de ces constructions ne sont pas définies , nous allons comprendre ces termes d'abord à la lumière de la norme C11:

séquencée: (5.1.2.3)

  

Compte tenu de tout deux évaluations A et B, si A est séquencée avant B, l'exécution de A doit précéder l'exécution de B.

non séquencée:

  

Si A ne séquencée avant ou après B, puis A et B sont non séquencée.

L'évaluation peut être l'une des deux choses:

  • calculs de valeur , qui fonctionnent sur le résultat d'une expression; et
  • effets secondaires , qui sont des modifications d'objets.

Séquence Point:

  

La présence d'un point de séquence entre l'évaluation des expressions A et B implique que tous les effets calcul de valeur et latérale associée à A est séquencé avant chaque valeur calcul et effet secondaire associés à B.

Venons-en maintenant à la question, les expressions comme

int i = 1;
i = i++;

norme indique que:

6.5 Expressions:

  

Si un effet secondaire sur un objet scalaire est non séquencée par rapport à un effet secondaire différent sur le même objet scalaire ou un calcul de valeur en utilisant la valeur de la même scalaire objet, le comportement est indéfini . [...]

Par conséquent, l'expression ci-dessus invoque UB parce que deux effets secondaires sur le même objet i est non séquencée par rapport à l'autre. Cela signifie qu'il n'est pas séquencée si l'effet secondaire par l'affectation à i sera effectuée avant ou après l'effet secondaire par ++.
Selon que la cession a lieu avant ou après l'augmentation, des résultats différents seront produits et c'est l'un des cas de comportement undefined .

Permet de renommer le i à gauche de l'assignation être il et à droite de l'affectation (dans l'expression i++) être ir, l'expression soit comme

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Un point importante en ce qui concerne l'opérateur de ++ Postfix est que:

  

juste parce que le ++ vient après la variable ne signifie pas que l'incrément arrive en retard . L'augmentation peut se produire dès que les goûts du compilateur tant que le compilateur assure que la valeur d'origine est utilisée .

Cela signifie que le il = ir++ d'expression peut être évaluée soit comme

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

ou

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

résultant en deux 1 et 2 des résultats différents qui dépend de la séquence d'effets secondaires par affectation et ++ et appelle donc UB.

Une autre façon de répondre à cela, plutôt que de se perdre dans les détails Arcane des points de séquence et un comportement non défini, est tout simplement de demander, ce sont-ils censés dire? Quel était le programmeur en essayant de le faire?

Le premier fragment interrogé sur, i = i++ + ++i, est assez bien fou dans mon livre. Personne ne jamais écrire dans un programme réel, il est pas évident ce qu'il fait, il n'y a pas d'algorithme concevable que quelqu'un aurait pu essayer de code qui aurait donné lieu à cette séquence particulière d'opérations artificielle. Et comme il est pas évident pour vous et moi ce qu'il est censé faire, il est bien dans mon livre si le compilateur ne peut pas comprendre ce qu'il est censé faire, que ce soit.

Le deuxième fragment, i = i++, est un peu plus facile à comprendre. Quelqu'un essaie clairement incrémenter i et affecter le résultat à i. Mais il y a deux manières de le faire en C. La façon la plus simple d'ajouter 1 à i, et affecter le résultat à i, est le même dans presque toutes les langues de programmation:

i = i + 1

C, bien sûr, a un raccourci pratique:

i++

Cela signifie, « ajouter 1 à i, et affecter le résultat à i ». Donc, si nous construisons un méli-mélo des deux, en écrivant

i = i++

ce que nous disons est « ajouter 1 à i, et affecter le résultat à i, et affecter le résultat à i ». Nous sommes confus, donc ça ne me dérange pas trop si le compilateur est confus, aussi.

De façon réaliste, la seule fois où ces expressions folles sont écrits est quand les gens les utilisent comme des exemples artificiels de la façon dont ++ est censé fonctionner. Et bien sûr, il est important de comprendre comment fonctionne ++. Mais une règle pratique pour l'utilisation ++ est, « Si ce n'est pas évident que l'expression en utilisant des moyens ++, ne l'écrivez pas. »

Nous avions l'habitude de passer d'innombrables heures à discuter des expressions comme forum comp.lang.c ces derniers et pourquoi ils sont indéfinis. Deux de mes réponses plus longues, qui tentent d'expliquer vraiment pourquoi, sont archivées sur le web:

Voir aussi question 3.8 et le reste des questions section 3 du C FAQ liste .

Souvent, cette question est liée comme un doublon de questions liées au code comme

printf("%d %d\n", i, i++);

ou

printf("%d %d\n", ++i, i++);

ou des variantes.

Bien que ce soit aussi un comportement non défini comme l'a déjà dit, il y a des différences subtiles quand printf() est impliqué lorsque l'on compare à un énoncé tel que:

x = i++ + i++;

Dans la déclaration suivante:

printf("%d %d\n", ++i, i++);

ordre d'évaluation des arguments dans printf() est non spécifié . Cela signifie, expressions i++ et ++i pourraient être évaluées dans un ordre quelconque. C11 norme a des descriptions pertinentes à ce sujet:

Annexe J, les comportements non spécifiés

  

L'ordre dans lequel l'indicateur de fonction, les arguments, et   des sous-expressions à l'intérieur des arguments sont évalués dans un appel de fonction   (6.5.2.2).

3.4.4, un comportement non spécifié

  

Utilisation d'une valeur non spécifiée, ou tout autre comportement lorsque cela   Norme internationale offre deux possibilités et imposeraient ou plus   pas d'autres exigences sur laquelle est choisie dans tous les cas.

     

Exemple Un exemple de comportement non spécifié est l'ordre dans lequel le   arguments d'une fonction sont évalués.

un comportement non spécifié est lui-même pas un problème. Considérez cet exemple:

printf("%d %d\n", ++x, y++);

Cela aussi a un comportement non spécifié parce que l'ordre d'évaluation des ++x et y++ est non spécifiée. Mais il est énoncé parfaitement légal et valide. Il y a pas un comportement non défini dans cette déclaration. Étant donné que les modifications (++x et y++) se font à distincts objets.

Ce qui rend la déclaration suivante

printf("%d %d\n", ++i, i++);

un comportement non défini est le fait que ces deux expressions modifient le même i objet sans intervention point de séquence .


Un autre détail est que le virgule impliqués dans l'appel printf () est un séparateur , pas opérateur virgule .

Cette distinction est importante parce que le opérateur virgule n'introduit point de séquence entre l'évaluation de leurs opérandes, ce qui rend le cadre juridique suivant:

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

L'opérateur virgule évalue ses opérandes de gauche à droite et les rendements que la valeur du dernier opérande. Donc, en j = (++i, i++);, par incréments de ++i i à 6 et les rendements de i++ ancienne valeur de i (6) qui est attribué à j. Alors i devient 7 en raison de post-incrémentation.

Donc, si le virgule dans l'appel de fonction devait être un opérateur virgule puis

printf("%d %d\n", ++i, i++);

ne sera pas un problème. Mais il invoque un comportement non défini parce que virgule ici est un séparateur .


Pour ceux qui sont nouveaux à un comportement non défini profiteraient à la lecture non définie, un comportement non spécifié et défini par l'implémentation est également pertinente

.

Il est peu probable que les compilateurs et les transformateurs se font effectivement, ce serait légal, en vertu de la norme C, pour le compilateur de mettre en œuvre « i ++ » avec la séquence:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Bien que je ne pense pas que les processeurs prennent en charge le matériel pour permettre une telle chose à faire efficacement, on peut facilement imaginer des situations où un tel comportement serait de rendre le code plus facile multi-thread (par exemple, il garantirait que si deux threads tentent de effectuer la séquence ci-dessus en même temps, i serait incrémentée par deux) et il est tout à fait inconcevable que pas un certain futur processeur pourrait fournir une caractéristique quelque chose comme ça.

Si le compilateur devait écrire i++ comme indiqué ci-dessus (juridique selon la norme) et devaient entremêler les instructions ci-dessus tout au long de l'évaluation de l'expression globale (également juridique), et si elle n'a pas eu lieu de constater que l'un des les autres instructions pour accéder i arrivé, il serait possible (et juridique) pour le compilateur de générer une séquence d'instructions qui impasse. Certes, un compilateur presque certainement détecter le problème dans le cas où la même i variable est utilisée dans les deux endroits, mais si une routine accepte des références à deux pointeurs p et q, et utilise (*p) et (*q) dans l'expression ci-dessus (plutôt que l'utilisation i deux fois) le compilateur ne serait pas tenu de reconnaître ou d'éviter l'impasse qui se produirait si l'adresse a été adoptée pour les p et q du même objet.

Le standard C indique qu'une variable ne doit être attribué au plus une fois entre deux points de séquence. Un exemple point-virgule est un point de la séquence.
Ainsi, chaque déclaration de la forme:

i = i++;
i = i++ + ++i;

et ainsi de suite violer cette règle. La norme indique également que le comportement est indéfini et non sans précision. Certains compilateurs ne détectent ces derniers et produisent un résultat mais ce n'est pas conforme à la norme.

Cependant, deux variables différentes peuvent être incrémentés entre deux points de séquence.

while(*src++ = *dst++);

Ce qui précède est une pratique courante de codage lors de la copie / l'analyse des chaînes.

Alors que la syntaxe des expressions comme a = a++ ou a++ + a++ est légal, comportement de ces constructions est undefined parce que < em> doit dans la norme C est pas respectée. C99 6.5p2 :

  
      
  1. Entre la séquence précédente et du prochain point d'un objet doit avoir sa valeur stockée modifiée au plus une fois par l'évaluation d'une expression. [72] En outre, la valeur antérieure doit être en lecture seule pour déterminer la valeur à stocker [73]
  2.   

note 73 précisant en outre que

  
      
  1. Ce paragraphe rend les expressions des états non définis tels que

    i = ++i + 1;
    a[i++] = i;
    
         

    tout en permettant

    i = i + 1;
    a[i] = i;
    
  2.   

Les différents points de séquence sont indiquées dans l'annexe C de C11 (et C99 ):

  
      
  1. Ce qui suit sont les points de séquence décrite dans 5.1.2.3:

         
        
    • Entre les évaluations de la fonction désignateur et arguments dans un appel de fonction et l'appel réel. (6.5.2.2).
    •   
    • Entre les évaluations des premier et second opérandes des opérateurs suivants: ET logique && (06/05/13); OU logique || (05/06/14); virgule, (6.5.17).
    •   
    • Entre les évaluations du premier opérande du conditionnel? :. Opérateur et celui des deuxième et troisième opérandes est évalué (06/05/15)
    •   
    • La fin d'un declarator complet: déclarateurs (6.7.6);
    •   
    • Entre l'évaluation d'une expression complète et la prochaine pleine expression à évaluer. Ci-dessous sont pleines expressions: un initialiseur qui ne fait pas partie d'un composé littéral (6.7.9); l'expression dans une instruction d'expression (6.8.3); l'expression de contrôle d'une instruction de sélection (si ou switch) (6.8.4); l'expression de contrôle d'un certain temps ou faire la déclaration (6.8.5); chacune des expressions d'une déclaration pour (facultatif) (6.8.5.3); (en option) expression dans une déclaration de retour (6.8.6.4).
    •   
    • immédiatement avant un retour de la fonction de bibliothèque (7.1.4).
    •   
    • Une fois que les actions associées à chaque spécificateur de conversion de fonction d'entrée / sortie formatée (7.21.6, 7.29.2).
    •   
    • immédiatement avant et immédiatement après chaque appel à une fonction de comparaison, et également entre un appel à une fonction de comparaison et de tout mouvement des objets passés comme arguments à cet appel (7.22.5).
    •   
  2.   

Le libellé de la même en C11 est :

  
      
  1. Si un effet secondaire sur un objet scalaire est non séquencée par rapport soit à un effet secondaire différent sur le même objet scalaire ou un calcul de valeur en utilisant la valeur d'un même objet scalaire, le comportement est indéfini. Si plusieurs ordres admissibles des sous-expressions d'une expression, le comportement est indéfini si un tel effet secondaire non séquencée se produit dans l'un des orderings.84)
  2.   

Vous pouvez détecter ces erreurs dans un programme par exemple en utilisant une version récente de GCC avec -Wall et -Werror, puis GCC refusera purement et simplement de compiler votre programme. Ce qui suit est la sortie de gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

La partie importante est de savoir ce point de séquence est - et ce qui est un point de séquence et ce pas . Par exemple, le opérateur virgule est un point de séquence, donc

j = (i ++, ++ i);

est bien défini, et incrémente i par un, ce qui donne la valeur ancienne, jeter cette valeur; puis à opérateur virgule, de régler les effets secondaires; puis incrémenter i par un, et la valeur résultante devient la valeur de l'expression - à savoir c'est juste un moyen artificiel d'écrire j = (i += 2) qui est encore une fois d'une manière « intelligente » d'écrire

i += 2;
j = i;

Cependant, le , dans les listes d'arguments de la fonction est pas un opérateur virgule, et il n'y a pas de point de séquence entre les évaluations des arguments distincts; au lieu de leurs évaluations sont non séquencée en ce qui concerne les uns aux autres; si l'appel de fonction

int i = 0;
printf("%d %d\n", i++, ++i, i);

a comportement non défini car il n'y a pas de point de séquence entre les évaluations des i++ et ++i dans les arguments de fonction , et la valeur de i est donc modifié deux fois, par les deux i++ et ++i, entre le précédent et le point suivant de la séquence.

https://stackoverflow.com/questions/29505280/incrementing-array-index- en-c quelqu'un a demandé au sujet d'une déclaration comme:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

qui imprime 7 ... l'OP devrait pour imprimer 6.

Les incréments de ++i ne sont pas garantis à tous complets avant que le reste des calculs. En fait, les différents compilateurs obtenir des résultats différents ici. Dans l'exemple que vous avez fourni, le premier 2 ++i exécuté, les valeurs de k[] ont été lus, la dernière ++i puis k[].

num = k[i+1]+k[i+2] + k[i+3];
i += 3

compilateurs modernes optimiseront très bien. En fait, peut-être mieux que le code que vous avez écrit à l'origine (en supposant qu'il avait travaillé comme vous l'aviez espéré).

Votre question est sans doute pas: « Pourquoi ces constructions un comportement non défini en C? ». Votre question était probablement: « Pourquoi ce code (en utilisant ++) ne m'a pas donné la valeur que je pensais? », Et quelqu'un a marqué votre question comme un doublon, et vous a envoyé ici.

Ce réponse tente de répondre à cette question: pourquoi votre code vous donne pas la réponse que vous attendiez, et comment pouvez-vous apprendre à reconnaître (et éviter) des expressions qui ne fonctionnent pas comme prévu <. / p>

Je suppose que vous avez entendu la définition de base des opérateurs de ++ et -- de C maintenant, et comment la forme préfixe ++x diffère de la forme postfix x++. Mais ces opérateurs sont difficiles à penser, pour ainsi assurer que vous avez compris, peut-être que vous avez écrit un programme de test tout petit impliquant quelque chose comme

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

Mais, à votre grande surprise, ce programme a fait pas vous aider à comprendre - il une sortie étrange imprimé, inattendue, inexplicable, ce qui suggère que peut-être ++ fait quelque chose de complètement différent, pas du tout ce que vous pensiez il l'a fait.

Ou, peut-être que vous regardez un disque à comprendre l'expression comme

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

Peut-être que quelqu'un vous a donné ce code comme un casse-tête. Ce code fait également aucun sens, surtout si vous l'exécutez - et si vous compilez et exécutez sous deux compilateurs différents, vous êtes susceptible d'obtenir deux réponses différentes! Quoi de neuf avec ça? Quelle réponse est correcte? (Et la réponse est que les deux sont, ou aucun d'entre eux sont.)

Comme vous l'avez entendu maintenant, toutes ces expressions sont non défini , ce qui signifie que le langage C ne fait aucune garantie quant à ce qu'ils vont faire. Ceci est un résultat étrange et surprenant, parce que vous avez probablement pensé que tout programme vous pourriez écrire, tant qu'il a compilé et a couru, générerait une sortie bien définie unique. Mais dans le cas d'un comportement non défini, c'est pas.

Ce qui rend une expression non définie? Sont des expressions impliquant ++ et -- toujours non défini? Bien sûr que non. Ce sont les opérateurs utiles, et si vous les utilisez correctement, ils sont parfaitement bien définies

Pour les expressions dont nous parlons ce qui les rend non défini est quand il y a trop de choses à la fois, quand nous ne sommes pas sûr de ce que les choses pour se produire, mais quand les questions d'ordre au résultat que nous obtenons.

Revenons aux deux exemples que j'ai utilisé dans cette réponse. Quand j'ai écrit

printf("%d %d %d\n", x, ++x, x++);

la question est, avant d'appeler printf, le compilateur calcule d'abord la valeur de x ou x++, ou ++x peut-être? Mais il se trouve nous ne savons pas . Il n'y a aucune règle en C qui dit que les arguments à une fonction évalués de la gauche à droite ou de droite à gauche, ou dans un autre ordre. Donc, nous ne pouvons pas dire si le compilateur fera x d'abord, puis ++x, puis x++ ou x++ ++x puis ensuite x, ou d'un autre ordre. Mais l'ordre des questions clairement, car en fonction de quel ordre les utilisations du compilateur, nous allons clairement obtenir des résultats différents imprimés par printf.

Qu'en est-il cette expression folle?

x = x++ + ++x;

Le problème avec cette expression est qu'il contient trois tentatives pour modifier la valeur de x: (1) la partie x++ tente d'ajouter 1 à x, stocker la nouvelle valeur dans x, et retourner l'ancienne valeur de x; (2) la partie de ++x tente d'ajouter 1 à x, mémoriser la nouvelle valeur dans x, et retourner la nouvelle valeur de x; et (3) la partie de x = tente d'affecter la somme des deux autres en arrière à x. Laquelle de ces trois missions seront tentées de « gagner »? Laquelle des trois valeurs seront effectivement obtenir assigned à x? Encore une fois, et peut-être étonnamment, il n'y a pas de règle en C pour nous dire.

Vous imaginez peut-être que la priorité ou l'associativité ou l'évaluation de gauche à droite vous indique ce que les choses se passent d'ordre, mais ils ne le font pas. Vous ne me croirez pas, mais s'il vous plaît prendre ma parole, et je vais le dire encore une fois: la priorité et l'associativité ne déterminent pas tous les aspects de l'ordre d'évaluation d'une expression en C. En particulier, si, dans l'expression il existe plusieurs différents endroits où nous essayons d'assigner une nouvelle valeur à quelque chose comme x, la priorité et l'associativité font pas nous dire quel de ces tentatives a lieu en premier, ou le dernier, ou quoi que ce soit.


Donc, avec tout ce contexte et l'introduction de la route, si vous voulez vous assurer que tous vos programmes sont bien définis, quelles expressions pouvez-vous écrire, et ceux qui peuvent vous pas écrire?

Ces expressions sont bien:

y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;

Ces expressions sont toutes non définies:

x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);

Et la dernière question est, comment pouvez-vous dire quelles expressions sont bien définies, et qui ne sont pas définies expressions?

Comme je l'ai dit plus tôt, les expressions non définies sont celles où il y a trop à la fois, où vous ne pouvez pas être sûr de ce que les choses se passent dans l'ordre, et où les questions d'ordre:

  1. S'il y a une variable qui devient modifié (affecté à) en deux ou plusieurs endroits différents, comment savez-vous que la modification a lieu en premier?
  2. S'il y a une variable qui devient modifiée en un seul endroit, et dont la valeur utilisée dans un autre endroit, comment savez-vous si elle utilise l'ancienne valeur ou la nouvelle valeur?

À titre d'exemple n ° 1, dans l'expression

x = x++ + ++x;

il y a trois tentatives pour modifier `x.

À titre d'exemple n ° 2, dans l'expression

y = x + x++;

nous utilisons tous deux la valeur de x et de le modifier.

Alors que la réponse: assurez-vous que dans une expression que vous écrivez, chaque variable est modifiée au plus une fois, et si une variable est modifiée, vous ne tentez pas aussi d'utiliser la valeur de cette variable ailleurs

Une bonne explication sur ce qui se passe dans ce genre de calcul est fourni dans le document de href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/" rel="noreferrer"> le site ISO W14 .

J'explique les idées.

La règle principale de la norme ISO 9899 norme applicable dans cette situation est 6.5p2.

  

Entre le précédent et suivant le point de séquence d'un objet doit posséder une valeur stockée modifiée au plus une fois par l'évaluation d'une expression. En outre, la valeur préalable est en lecture seule pour déterminer la valeur à stocker.

Les points de séquence dans une expression comme i=i++ sont avant et après i= i++.

Dans le document cité ci-dessus que je il est expliqué que vous pouvez déterminer le programme comme étant formés par des petites boîtes, chaque boîte contenant les instructions entre 2 points de séquence consécutifs. Les points de séquence sont définis dans l'annexe C de la norme, dans le cas de i=i++ il y a 2 points de séquence qui délimitent une pleine expression. Une telle expression est syntaxiquement équivalent à une entrée de expression-statement sous la forme Backus-Naur de la grammaire (une grammaire est fournie dans l'annexe A de la norme).

l'ordre des instructions dans une boîte n'a pas d'ordre clair.

i=i++

peut être interprété comme

tmp = i
i=i+1
i = tmp

ou comme

tmp = i
i = tmp
i=i+1

parce que les deux toutes ces formes pour interpréter le i=i++ de code sont valides et parce qu'elles donnent des réponses différentes, le comportement est indéfini.

Ainsi, un point de séquence peut être vu par le début et la fin de chaque boîte qui compose le programme [les boîtes sont des unités atomiques en C] et à l'intérieur d'une boîte de l'ordre d'instructions n'a pas été défini dans tous les cas. Modification de l'ordre, on peut parfois changer le résultat.

EDIT:

Autre bonne source pour expliquer ces ambiguïtés sont les entrées de c-faq site (également publié comme un livre ), à savoir ici et ici et .

La raison en est que le programme est en cours d'exécution un comportement non défini. Le problème réside dans l'ordre d'évaluation, car il n'y a pas de points de séquence requise conformément à la norme 98 C ++ (aucune opération est séquencé avant ou après l'autre selon la 11 terminologie C ++).

Cependant, si vous vous en tenez à un compilateur, vous trouverez le comportement persistant, aussi longtemps que vous n'ajoutez pas les appels de fonction ou des pointeurs, ce qui rendrait le comportement plus désordonné.

  • Alors d'abord le CCG: En utilisant Nuwen MinGW 15 GCC 7.1 vous obtiendrez:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2
    

    }

Comment fonctionne le GCC? il évalue les sous expressions à un ordre de gauche à droite pour le côté droit (RHS), affecte ensuite la valeur sur le côté gauche (LHS). Ceci est exactement comment se comportent Java et C # et de définir leurs normes. (Oui, le logiciel équivalent en Java et C # a défini les comportements). Il évalue chaque sous expression, un par un dans l'énoncé RHS dans un ordre de gauche à droite; pour chaque sous expression: le C ++ (pré-incrémentation) est évaluée en premier, puis la valeur c est utilisée pour l'opération, puis l'incrément de post c ++)

.

GCC C ++: Les opérateurs

  

Dans GCC C ++, la priorité des opérateurs contrôle l'ordre dans   où les opérateurs individuels sont évalués

le code équivalent à comportement défini comme C ++ GCC comprend:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Ensuite, nous allons Visual Studio. Visual Studio 2015, vous obtenez:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Comment fonctionne le studio visuel, il faut une autre approche, il évalue toutes les expressions pré-incréments de premier passage, puis utilise des valeurs variables dans les opérations en deuxième passage, assigner de RHS à LHS à la troisième passe, puis au dernier passage, il évalue toutes les expressions post-incrément en une seule passe.

l'équivalent en C ++ défini le comportement comme Visual C ++ comprend:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

comme des états de documentation Visual Studio précédence et Ordre d'évaluation :

  

Lorsque plusieurs opérateurs apparaissent ensemble, ils ont la même priorité et sont évalués en fonction de leur associativité. Les opérateurs du tableau sont décrits dans les sections commençant par Postfix opérateurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top