Pourquoi c = ++ (a + b) donne-t-il une erreur de compilation?

111

Après des recherches, j'ai lu que l'opérateur d'incrémentation nécessite que l'opérande ait un objet de données modifiable: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

À partir de là, je suppose que cela donne une erreur de compilation car il (a+b)s'agit d'un entier temporaire et n'est donc pas modifiable.

Cette compréhension est-elle correcte? C'était la première fois que j'essayais de rechercher un problème, donc s'il y avait quelque chose que j'aurais dû chercher, veuillez le conseiller.

dng
la source
35
Ce n'est pas mal en termes de recherche. Tu es sur la bonne piste.
StoryTeller - Unslander Monica
35
Qu'attendez-vous de l'expression?
qrdl
4
selon la norme C11 6.5.3.1: L'opérande de l'opérateur d'incrémentation ou de décrémentation du préfixe doit être de type réel ou pointeur atomique, qualifié ou non qualifié, et doit être une valeur l modifiable
Christian Gibbons
10
Comment aimeriez-vous que le 1 soit réparti entre a et b? "Les indices de tableau devraient-ils commencer à 0 ou 1? Mon compromis de 0,5 a été rejeté sans, pensai-je, une considération appropriée." - Stan Kelly-Bootle
Andrew Morton
5
Je pense qu'une question de suivi est de savoir pourquoi voudriez-vous faire cela lorsque c = a + b + 1votre intention est plus claire et plus courte à taper. Les opérateurs d'incrémentation / décrémentation font deux choses: 1. eux et leur argument forment une expression (qui peut être utilisée, par exemple dans une boucle for), 2. ils modifient l'argument. Dans votre exemple, vous utilisez la propriété 1. mais pas la propriété 2., puisque vous rejetez l'argument modifié. Si vous n'avez pas besoin de la propriété 2. et que vous voulez simplement l'expression, vous pouvez simplement écrire une expression, par exemple x + 1 au lieu de x ++.
Trevor

Réponses:

117

C'est juste une règle, c'est tout, et est peut-être là pour (1) faciliter l'écriture des compilateurs C et (2) personne n'a convaincu le comité de normalisation C de l'assouplir.

De manière informelle, vous ne pouvez écrire que ++foosi foopeut apparaître sur le côté gauche d'une expression d'affectation comme foo = bar. Puisque vous ne pouvez pas écrire a + b = bar, vous ne pouvez pas écrire non ++(a + b)plus.

Il n'y a pas de vraie raison pour laquelle a + bne pourrait pas produire un temporaire sur lequel ++peut fonctionner, et le résultat de cela est la valeur de l'expression ++(a + b).

Bathsheba
la source
4
Je pense que le point (1) touche le clou sur la tête. Le simple fait de regarder les règles de matérialisation temporaire en C ++ peut tourner l'estomac (mais c'est puissant, je dois le dire).
StoryTeller - Unslander Monica
4
@StoryTeller: En effet, contrairement à notre langage bien-aimé C ++, C se compile toujours en assemblage de manière relativement triviale.
Bathsheba
29
Voici une vraie raison à mon humble avis: ce serait une terrible confusion si ++parfois avait un effet secondaire de modifier quelque chose et parfois tout simplement pas.
aschepler
5
@dng: C'est vrai; c'est pourquoi les termes lvalue et rvalue ont été introduits, bien que les choses soient plus compliquées que cela de nos jours (en particulier en C ++). Par exemple, une constante ne peut jamais être une valeur l: quelque chose comme 5 = a n'a aucun sens.
Bathsheba
6
@Bathsheba Cela explique pourquoi 5 ++ provoque également une erreur de compilation
dng
40

La norme C11 indique dans la section 6.5.3.1

L'opérande de l'opérateur d'incrémentation ou de décrémentation du préfixe doit être de type réel ou pointeur atomique, qualifié ou non qualifié, et doit être une valeur l modifiable

Et "lvalue modifiable" est décrite dans la section 6.3.2.1 sous-section 1

Une lvalue est une expression (avec un type d'objet autre que void) qui désigne potentiellement un objet; si une lvalue ne désigne pas un objet lors de son évaluation, le comportement n'est pas défini. Lorsqu'un objet est dit avoir un type particulier, le type est spécifié par la lvalue utilisée pour désigner l'objet. Une lvalue modifiable est une lvalue qui n'a pas de type tableau, n'a pas de type incomplet, n'a pas de type qualifié const et s'il s'agit d'une structure ou d'une union, n'a aucun membre (y compris, récursivement, aucun membre ou élément de tous les agrégats ou unions contenus) avec un type qualifié const.

Donc (a+b)n'est pas une lvalue modifiable et n'est donc pas éligible pour l'opérateur d'incrémentation de préfixe.

Christian Gibbons
la source
1
Votre conclusion à partir de ces définitions est manquante ... Vous voulez dire que (a + b) ne désigne pas potentiellement un objet, mais ces paragraphes ne le permettent pas.
hkBst
21

Vous avez raison. la ++tente d'attribuer la nouvelle valeur à la variable d' origine. Donc ++a, prendra la valeur de a, y ajoutera 1, puis l'attribuera à nouveau a. Puisque, comme vous l'avez dit, (a + b) est une valeur temporaire et non une variable avec une adresse mémoire attribuée, l'affectation ne peut pas être effectuée.

Roee Gavirel
la source
12

Je pense que vous avez principalement répondu à votre propre question. Je pourrais faire un petit changement dans votre formulation et remplacer "variable temporaire" par "rvalue" comme mentionné par C.Gibbons.

Les termes variable, argument, variable temporaire et ainsi de suite deviendront plus clairs au fur et à mesure que vous en apprendrez sur le modèle de mémoire de C (cela ressemble à un bel aperçu: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

Le terme «rvalue» peut sembler opaque lorsque vous débutez, donc j'espère que ce qui suit vous aidera à développer une intuition à ce sujet.

Lvalue / rvalue parlent des différents côtés d'un signe égal (opérateur d'affectation): lvalue = côté gauche (L minuscule, pas un "un") rvalue = côté droit

Apprendre un peu comment C utilise la mémoire (et les registres) sera utile pour voir pourquoi la distinction est importante. Dans de larges coups de pinceau , le compilateur crée une liste d'instructions en langage machine qui calculent le résultat d'une expression (la rvalue), puis place ce résultat quelque part (la lvalue). Imaginez un compilateur traitant le fragment de code suivant:

x = y * 3

Dans le pseudocode d'assemblage, cela pourrait ressembler à cet exemple de jouet:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

L'opérateur ++ (et son équivalent -) a besoin d'un "quelque part" à modifier, essentiellement tout ce qui peut fonctionner comme une lvalue.

Comprendre le modèle de mémoire C sera utile car vous aurez une meilleure idée de la façon dont les arguments sont passés aux fonctions et (éventuellement) comment travailler avec l'allocation de mémoire dynamique, comme la fonction malloc (). Pour des raisons similaires, vous pouvez étudier une programmation d'assemblage simple à un moment donné pour avoir une meilleure idée de ce que fait le compilateur. De plus, si vous utilisez gcc , l' option -S "Arrêtez après l'étape de compilation proprement dite; ne pas assembler." peut être intéressant (même si je recommanderais de l'essayer sur un petit fragment de code).

En passant: l'instruction ++ existe depuis 1969 (bien qu'elle ait commencé dans le prédécesseur de C, B):

L'observation (de Ken Thompson) était que la traduction de ++ x était plus petite que celle de x = x + 1. "

Suite à cette référence wikipedia vous amènera à un article intéressant de Dennis Ritchie (le «R» dans «K&R C») sur l'histoire du langage C, lié ici pour plus de commodité: http://www.bell-labs.com/ usr / dmr / www / chist.html où vous pouvez rechercher "++".

jgreve
la source
6

La raison en est que la norme exige que l'opérande soit une valeur l. L'expression (a+b)n'est pas une lvalue, donc l'application de l'opérateur d'incrément n'est pas autorisée.

Maintenant, on pourrait dire « OK, c'est bien la raison, mais il est en fait pas * raison réelle * autre que » , mais malheureusement le libellé particulier de la façon dont l'opérateur travaille factuellement n'exige que ce soit le cas.

L'expression ++ E équivaut à (E + = 1).

Evidemment, vous ne pouvez pas écrire E += 1si ce En'est pas une lvalue. Ce qui est dommage car on aurait tout aussi bien pu dire: "incrémente E de un" et c'est fait. Dans ce cas, appliquer l'opérateur sur une valeur non-l serait (en principe) parfaitement possible, au détriment de rendre le compilateur légèrement plus complexe.

Maintenant, la définition pourrait être reformulée de manière triviale (je pense que ce n'est même pas à l'origine C mais un héritage de B), mais cela changerait fondamentalement le langage en quelque chose qui n'est plus compatible avec ses anciennes versions. Étant donné que les avantages possibles sont plutôt faibles mais que les implications possibles sont énormes, cela ne s'est jamais produit et ne se produira probablement jamais.

Si vous considérez C ++ en plus de C (la question est étiquetée C, mais il y a eu une discussion sur les surcharges d'opérateurs), l'histoire devient encore plus compliquée. En C, il est difficile d'imaginer que cela puisse être le cas, mais en C ++, le résultat de (a+b)pourrait très bien être quelque chose que vous ne pouvez pas incrémenter du tout, ou l'incrémentation pourrait avoir des effets secondaires très considérables (pas seulement l'ajout de 1). Le compilateur doit être capable de faire face à cela et de diagnostiquer les cas problématiques au fur et à mesure qu'ils se produisent. Sur une lvalue, c'est encore un peu trivial à vérifier. Ce n'est pas le cas pour toute sorte d'expression aléatoire à l'intérieur d'une parenthèse que vous lancez sur le pauvre.
Ce n'est pas une vraie raison pour laquelle ça ne pourrait pas être fait, mais cela explique certainement pourquoi les gens qui l'ont implémenté ne sont pas précisément ravis d'ajouter une telle fonctionnalité qui promet très peu d'avantages à très peu de gens.

Damon
la source
3

(a + b) donne une valeur r, qui ne peut pas être incrémentée.

Casper B. Hansen
la source
3

++ essaie de donner la valeur à la variable d'origine et puisque (a + b) est une valeur temporaire, il ne peut pas effectuer l'opération. Et ce sont essentiellement des règles des conventions de programmation C pour faciliter la programmation. C'est tout.

Babu Chandermani
la source
2

Lorsque l'expression ++ (a + b) est exécutée, alors par exemple:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
Jeet Parikh
la source