Pourquoi a = (b ++) a-t-il le même comportement que a = b ++?

J’écris une petite application de test en C avec GCC 4.8.4 préinstallé sur mon Ubuntu 14.04. Et je me suis perdu pour le fait que l’expression a=(b++); se comporte de la même manière que a=b++; Est-ce que. Le code simple suivant est utilisé:

 #include  #include  int main(int argc, char* argv[]){ uint8_t a1, a2, b1=10, b2=10; a1=(b1++); a2=b2++; printf("a1=%u, a2=%u, b1=%u, b2=%u.\n", a1, a2, b1, b2); } 

Le résultat après la compilation de gcc est a1=a2=10 , tandis que b1=b2=11 . Cependant, je m’attendais à ce que les parenthèses aient b1 incrémenté avant que sa valeur ne soit affectée à a1 .

À savoir, a1 devrait être 11 alors que a2 est égal à 10 .

Est-ce que quelqu’un a une idée de ce problème?

Citant le C99: 6.5.2.4:

Le résultat de l’opérateur postfix ++ est la valeur de l’opérande. Une fois le résultat obtenu, la valeur de l’opérande est incrémentée. (C’est-à-dire que la valeur 1 du type approprié y est ajoutée.) Pour plus d’informations sur les contraintes, les types, les conversions et les effets des opérations sur les pointeurs, reportez-vous à la discussion sur les opérateurs additifs et l’affectation d’un composé. L’effet secondaire de la mise à jour de la valeur stockée de l’opérande doit se produire entre les points de séquence précédent et suivant.

Vous pouvez consulter le C99: annexe C pour comprendre quels sont les points de séquence valides.

Dans votre question, le simple ajout de parenthèses ne modifie pas les points de séquence, mais uniquement les points ; le personnage fait ça.

Ou, en d’autres termes, vous pouvez l’afficher comme s’il existait une copie temporaire de b et l’effet secondaire est l’original b incrémenté. Mais jusqu’à ce qu’un sharepoint séquence soit atteint, toute l’évaluation est effectuée sur la copie temporaire de b . La copie temporaire de b est alors rejetée, l’effet secondaire, c’est-à-dire que l’opération d’incrémentation, est validée dans la mémoire lorsqu’un sharepoint séquence est atteint.

Cependant, je m’attendais à ce que les parenthèses aient b1 incrémenté avant que sa valeur ne soit affectée à a1

Vous ne devriez pas vous attendre à ce que: le fait de placer des parenthèses autour d’une expression d’incrément ne modifie pas l’application de ses effets secondaires.

Les effets secondaires (dans ce cas, cela signifie écrire 11 dans b1 ) sont appliqués un certain temps après avoir récupéré la valeur actuelle de b1 . Cela peut se produire avant ou après l’évaluation complète de l’expression d’affectation. C’est pourquoi une post-incrémentation restra une post-incrémentation, avec ou sans parenthèses. Si vous vouliez une pré-incrémentation, placez ++ devant la variable:

 a1 = ++b1; 

Les parenthèses peuvent être difficiles à penser. Mais ils ne veulent pas dire “assurez-vous que tout se passe à l’intérieur en premier”.

Supposons que nous ayons

 a = b + c * d; 

La priorité plus élevée de la multiplication sur l’addition nous indique que le compilateur s’arrangera pour multiplier c par d, puis appenda le résultat à b. Si nous voulons l’autre interprétation, nous pouvons utiliser des parenthèses:

 a = (b + c) * d; 

Mais supposons que nous ayons des appels de fonction dans le mix. C’est, supposons que nous écrivions

  a = x() + y() * z(); 

Maintenant, s’il est clair que la valeur de retour de y () sera multipliée par la valeur de retour de z (), pouvons-nous dire quelque chose sur l’ordre dans lequel x (), y () et z () seront appelés? La réponse est non, nous ne pouvons absolument pas! En cas de doute, je vous invite à l’essayer en utilisant les fonctions x, y et z suivantes:

 int x() { printf("this is x()\n"); return 2; } int y() { printf("this is y()\n"); return 3; } int z() { printf("this is z()\n"); return 4; } 

La première fois que j’ai essayé cela, en utilisant le compilateur devant moi, j’ai découvert que la fonction x () était appelée en premier, même si le résultat était nécessaire en dernier. Quand j’ai changé le code d’appel en

  a = (x() + y()) * z(); 

l’ordre des appels à x, y et z est resté exactement le même, le compilateur vient de prendre des dispositions pour combiner leurs résultats différemment.

Enfin, il est important de réaliser que des expressions comme i++ deux effets: elles prennent la valeur de i et lui ajoutent 1, puis elles stockent la nouvelle valeur dans i . Mais le retour en magasin ne se produit pas nécessairement tout de suite, cela peut arriver plus tard. Et la question de “quand le magasin retourne-t-il exactement?” est un peu comme la question de “quand la fonction x est appelée?”. Vous ne pouvez pas vraiment savoir, cela dépend du compilateur, cela n’a généralement pas d’importance, cela diffère d’un compilateur à l’autre, si vous en tenez à vous, vous allez devoir faire autre chose pour forcer la commande.

Et dans tous les cas, rappelez-vous que la définition de i++ est qu’il donne l’ ancienne valeur de i out à l’expression environnante. C’est une règle assez absolue, et il ne peut pas être changé simplement en ajoutant des parenthèses! Ce n’est pas ce que font les parenthèses.

Revenons à l’exemple précédent impliquant les fonctions x, y et z. J’ai remarqué que la fonction x s’appelait en premier. Supposons que je ne veuille pas cela, supposons que je souhaite appeler les fonctions y et z en premier. Pourrais-je y parvenir en écrivant

 x = z() + ((y() * z())? 

Je pourrais écrire ça, mais ça ne change rien. Rappelez-vous que les parenthèses ne veulent pas dire “faites tout d’ abord à l’intérieur”. Ils font que la multiplication se produise avant l’ajout, mais le compilateur allait déjà le faire de cette façon de toute façon, en se basant sur la priorité plus élevée de la multiplication sur l’addition.

En haut, j’ai dit: “Si vous vous en souciez vraiment, vous allez devoir faire autre chose pour forcer l’ordre”. En général, vous devez utiliser des variables temporaires et des instructions supplémentaires. (Le terme technique est “insérer des points de séquence .”) Par exemple, pour que y et z soient appelés en premier, je pourrais écrire

 c = y(); d = z(); b = x(); a = b + c * d; 

Dans votre cas, si vous voulez vous assurer que la nouvelle valeur de b est assignée à a, vous pouvez écrire

 c = b++; a = b; 

Mais bien sûr, c’est idiot – si tout ce que vous voulez faire est d’incrémenter b et que sa nouvelle valeur soit atsortingbuée à a, c’est à cela que sert le préfixe ++ :

 a = ++b; 

Vos attentes sont complètement sans fondement.

Les parenthèses n’ont pas d’effet direct sur l’ordre d’exécution. Ils n’introduisent pas de points de séquence dans l’expression et n’obligent donc aucun effet secondaire à se matérialiser plus tôt qu’ils ne l’auraient été sans parenthèses.

De plus, par définition, l’expression post-incrémentation b++ évalue la valeur d’ origine de b . Cette exigence restra en place quel que soit le nombre de paires de parenthèses ajoutées autour de b++ . Même si les parenthèses “forçaient” en quelque sorte un incrément instantané, le langage aurait toujours besoin de (((b++))) pour évaluer l’ ancienne valeur de b , ce qui signifie qu’il serait toujours garanti à a de recevoir la valeur non incrémentée de b .

Les parenthèses n’affectent que le groupement syntaxique entre les opérateurs et leurs opérandes. Par exemple, dans votre expression originale a = b++ on peut immédiatement demander si le ++ s’applique à b seul ou au résultat de a = b . Dans votre cas, en ajoutant les parenthèses, vous avez simplement forcé explicitement l’opérateur ++ à appliquer à l’opérande b . Cependant, selon la syntaxe du langage (et la priorité et l’associativité de l’opérateur qui en découlent), ++ s’applique déjà à b , c’est-à-dire qu’unary ++ a une priorité plus élevée que binary = . Vos parenthèses ne changent rien, elles ne font que réitérer le regroupement qui existait déjà implicitement. D’où aucun changement de comportement.

Les parenthèses sont entièrement syntaxiques . Ils ne font que regrouper des expressions et sont utiles si vous souhaitez remplacer la priorité ou l’associativité des opérateurs. Par exemple, si vous utilisez des parenthèses ici:

 a = 2*(b+1); 

vous voulez dire que le résultat de b+1 doit être doublé, alors que si vous omettez les parenthèses:

 a = 2*b+1; 

vous voulez dire que seulement b devrait être doublé et ensuite le résultat devrait être incrémenté. Les deux arbres de syntaxe pour ces assignations sont:

  = = / \ / \ a * a + / \ / \ 2 + * 1 / \ / \ b 1 2 b a = 2*(b+1); a = 2*b+1; 

En utilisant des parenthèses, vous pouvez donc modifier l’arbre de syntaxe qui correspond à votre programme et une syntaxe différente peut correspondre à une sémantique différente.

Par contre, dans votre programme:

 a1 = (b1++); a2 = b2++; 

les parenthèses sont redondantes car l’opérateur d’assignation a une priorité inférieure à l’incrément de postfix ( ++ ). Les deux affectations sont équivalentes. dans les deux cas, l’arbre de syntaxe correspondant est le suivant:

  = / \ a ++ (postfix) | b 

Maintenant que nous en avons terminé avec la syntaxe, passons à la sémantique . Cette instruction signifie: évalue b++ et affecte le résultat à a . L’évaluation de b++ renvoie la valeur actuelle de b (qui est 10 dans votre programme) et incrémente b (par conséquent, b (qui devient maintenant 11). La valeur renvoyée (c’est-à-dire 10) est affectée à a . C’est ce que vous observez et c’est le comportement correct.

Cependant, je m’attendais à ce que les parenthèses aient b1 incrémenté avant que sa valeur ne soit affectée à a1.

Vous n’affectez pas b1 à a1 : vous affectez le résultat de l’expression postincrement.

Considérez le programme suivant, qui affiche la valeur de b lors de l’exécution de l’affectation:

 #include  using namespace std; int b; struct verbose { int x; void operator=(int y) { cout << "b is " << b << " when operator= is executed" << endl; x = y; } }; int main() { // your code goes here verbose a; b = 10; a = b++; cout << "a is " << ax << endl; return 0; } 

Je soupçonne qu'il s'agit d'un comportement non défini, mais néanmoins, lorsque j'utilise ideone.com, le résultat ci-dessous s'affiche.

 b is 11 when operator= is executed a is 10 

OK, en un mot: b++ est une expression unaire , et les parenthèses qui l’entourent n’auront aucune influence sur la priorité des opérations arithmétiques, car l’opérateur d’incrémentation ++ a l’une des priorités les plus élevées (sinon la plus élevée) en C. dans a * (b + c ), le (b + c) est une expression binary (à ne pas confondre avec le système de numérotation binary!) en raison d’une variable b et de son ajout c . On peut donc s’en souvenir facilement ainsi: les parenthèses entourant les expressions binary, ternaire, quaternaire … + INF auront presque toujours une influence sur la précédence (*); les parenthèses autour des unaires ne le seront JAMAIS – car elles sont “assez fortes” pour “résister” au regroupement entre parenthèses.

(*) Comme d’habitude, il existe quelques exceptions à la règle, ne serait-ce que quelques-unes: par exemple, -> (pour accéder aux membres des pointeurs sur des structures) a une très forte liaison, bien qu’il s’agisse d’un opérateur binary. Cependant, les débutants en C risquent fort de prendre un certain temps avant de pouvoir écrire un -> dans leur code, car ils auront besoin d’une compréhension avancée des pointeurs et des structures.

Les parenthèses ne changeront pas le comportement post-incrémentation lui-même.

a1 = (b1 ++); // b1 = 10

Cela équivaut à

  uint8_t mid_value = b1++; //10 a1 = (mid_value); //10 

Placer ++ à la fin d’une instruction (appelé post-incrémentation) signifie que l’incrément doit être effectué après l’instruction.

Même si la variable est entre parenthèses, cela ne change rien au fait qu’elle sera incrémentée une fois l’instruction terminée.

De learn.geekinterview.com :

Sous la forme postfixée, l’incrément ou décrément a lieu après l’utilisation de la valeur dans l’évaluation de l’expression.

Dans l’opération d’incrémentation ou de décrémentation du préfixe, l’incrémentation ou la décrémentation a lieu avant que la valeur ne soit utilisée dans l’évaluation de l’expression.

C’est pourquoi a = (b++) et a = b++ ont le même comportement.

Dans votre cas, si vous voulez d’abord incrémenter b , vous devez utiliser une pré-incrémentation, ++b au lieu de b++ ou (b++) .

Changement

 a1 = (b1++); 

à

 a1 = ++b1; // b will be incremented before it is assigned to a. 

Pour faire court: b ++ est incrémenté une fois l’instruction terminée

Mais même après cela, le résultat de b ++ est mis à a.

A cause de cela, les parenthèses ne changent pas la valeur ici.

(Vous pouvez le voir si vous regardez le bytecode java des opérations)