web-dev-qa-db-fra.com

Pourquoi les littéraux et les variables temporaires ne sont-ils pas des valeurs l?

J'ai lu que lvalues sont des "choses avec un emplacement de stockage défini".

Et aussi que les variables littérales et temporelles ne sont pas des valeurs l, mais aucune raison n'est donnée pour cette déclaration.

Est-ce parce que les littéraux et les variables temporaires n'ont pas d'emplacement de stockage défini? Si oui, où résident-ils sinon en mémoire?

Je suppose qu'il y a une certaine signification à "défini" dans "emplacement de stockage défini", s'il y a (ou n'est pas) s'il vous plaît faites le moi savoir.

24
pasha

Et aussi que les variables littérales et temporelles ne sont pas des valeurs l, mais aucune raison n'est donnée pour cette déclaration.

Cela est vrai pour tous les temporaires et littéraux, sauf pour les littéraux de chaîne. Ce sont en fait des valeurs (ce qui est expliqué ci-dessous).

Est-ce parce que les variables littérales et temporaires n'ont pas d'emplacement de stockage défini? Si oui, où résident-ils sinon en mémoire?

Oui. Le littéral 2 N'existe pas réellement; c'est juste une valeur dans le code source. Comme c'est une valeur, pas un objet, il ne doit pas avoir de mémoire associée. Il peut être codé en dur dans l'assembly créé par le compilateur, ou il peut être placé quelque part, mais comme cela n'est pas obligatoire, tout ce que vous pouvez faire est de le traiter comme une valeur pure, pas un objet.

Il y a cependant une exemption et c'est des littéraux de chaîne. Ceux-ci ont en fait du stockage car un littéral de chaîne est un tableau de const char[N]. Vous pouvez prendre l'adresse d'un littéral de chaîne et un littéral de chaîne peut se désintégrer en un pointeur, c'est donc une valeur l, même si elle n'a pas de nom.

Les temporaires sont également des valeurs. Même s'ils existent en tant qu'objets, leur emplacement de stockage est éphémère. Ils ne durent que jusqu'à la fin de l'expression complète dans laquelle ils se trouvent. Vous n'êtes pas autorisé à prendre leur adresse et ils n'ont pas non plus de nom. Ils pourraient même ne pas exister: par exemple, dans

Foo a = Foo();

La Foo() peut être supprimée et le code sémantiquement transformé en

Foo a(); // you can't actually do this since it declares a function with that signature.

alors maintenant, il n'y a même pas d'objet temporaire dans le code optimisé.

20
NathanOliver

Pourquoi les littéraux et les variables temporaires ne sont-ils pas des valeurs l?

J'ai deux réponses: parce que cela n'aurait pas de sens (1) et parce que la norme le dit (2). Concentrons-nous sur (1).

Est-ce parce que les variables littérales et temporaires n'ont pas d'emplacement de stockage défini?

Il s'agit d'une simplification qui ne convient pas ici. Une simplification qui le ferait: les littéraux et les temporaires ne sont pas des valeurs car il ne serait pas logique de les modifier1.

Quelle est la signification de 5++? Quelle est la signification de Rand() = 0? La norme dit que les valeurs temporaires et littérales ne sont pas des valeurs, donc ces exemples ne sont pas valides. Et chaque développeur de compilateur est plus heureux.


1) Vous pouvez définir et utiliser des types définis par l'utilisateur de manière à ce que la modification d'un temporaire ait un sens. Ce temporaire vivrait jusqu'à l'évaluation de la pleine expression. François Andrieux fait une belle analogie entre appeler f(MyType{}.mutate()) d'une part et f(my_int + 1) d'autre part. Je pense que la simplification tient toujours car MyType{}.mutate() peut être vu comme un autre temporaire comme MyType{} Était, comme my_int + 1 Peut être vu comme un autre int comme my_int. Tout cela est basé sur la sémantique et l'opinion. La vraie réponse est: (2) parce que la norme le dit.

9
YSC

Il y a beaucoup d'idées fausses courantes dans la question et dans les autres réponses; ma réponse espère répondre à cela.

Les termes lvalue et rvalue sont catégories d'expression . Ce sont des termes qui s'appliquent aux expressions. Pas aux objets. (Un peu confus, le terme officiel pour les catégories d'expression est "catégories de valeur"!)

Le terme objet temporaire fait référence aux objets. Cela inclut les objets de type classe, ainsi que les objets de type intégré. Le terme temporaire (utilisé comme nom) est l'abréviation de objet temporaire. Parfois, le terme autonome valeur est utilisé pour faire référence à un objet temporaire de type intégré. Ces termes s'appliquent aux objets, pas aux expressions.

La norme C++ 17 est plus cohérente dans la terminologie des objets que les normes précédentes, par ex. voir [conv.rval]/1. Il essaie maintenant d'éviter de dire valeur autrement que dans le contexte valeur d'une expression.


Maintenant, pourquoi y a-t-il différentes catégories d'expression? Un programme C++ est composé d'une collection d'expressions, jointes les unes aux autres avec des opérateurs pour créer des expressions plus grandes; et s'inscrivant dans un cadre de constructions déclaratives. Ces expressions créent, détruisent et effectuent d'autres manipulations sur des objets. La programmation en C++ pourrait être décrite comme utilisant des expressions pour effectuer des opérations avec des objets.

La raison pour laquelle les catégories d'expressions existent est de fournir un cadre d'utilisation des expressions pour exprimer les opérations que le programmeur a l'intention. Par exemple, dans les jours C (et probablement plus tôt), les concepteurs de langage ont pensé que 3 = 5; N'avait aucun sens dans le cadre d'un programme, il a donc été décidé de limiter le type d'expression pouvant apparaître à gauche. -à côté de =, et demandez au compilateur de signaler une erreur si cette restriction n'a pas été respectée.

Le terme lvalue est né à cette époque, bien que maintenant avec le développement de C++ il existe une vaste gamme d'expressions et de contextes où les catégories d'expression sont utiles, pas seulement le côté gauche d'un opérateur d'affectation .

Voici du code C++ valide: std::string("3") = std::string("5");. Sur le plan conceptuel, cela ne diffère pas de 3 = 5;, Mais il est autorisé. L'effet est qu'un objet temporaire de type std::string Et de contenu "3" Est créé, puis cet objet temporaire est modifié pour avoir du contenu "5", Puis l'objet temporaire est détruit . Le langage aurait pu être conçu pour que le code 3 = 5; Spécifie une série d'événements similaire (mais ce n'était pas le cas).


Pourquoi l'exemple string est-il légal, mais pas l'exemple int?

Chaque expression doit avoir une catégorie. La catégorie d'une expression peut ne pas sembler avoir une raison évidente au début, mais les concepteurs de la langue ont donné à chaque expression une catégorie selon ce qu'ils pensent être un concept utile à exprimer et ce qui ne l'est pas.

Il a été décidé que la séquence d'événements dans 3 = 5; Comme décrit ci-dessus n'est pas quelque chose que quiconque voudrait faire, et si quelqu'un a écrit une telle chose, alors il a probablement fait une erreur et signifiait autre chose, donc le compilateur devrait aider en donnant un message d'erreur.

Maintenant, la même logique pourrait conclure que std::string("3") = std::string("5") n'est pas quelque chose que quiconque voudrait faire non plus. Cependant, un autre argument est que pour un autre type de classe, T(foo) = x; pourrait en fait être une opération utile, par exemple parce que T pourrait avoir un destructeur qui fait quelque chose. Il a été décidé que l'interdiction de cette utilisation pourrait être plus nuisible aux intentions d'un programmeur que bonne. (Que ce soit une bonne décision ou non est discutable; voir cette question pour discussion).


Maintenant, nous nous rapprochons pour enfin répondre à votre question :)

Qu'il y ait ou non de la mémoire ou un emplacement de stockage n'est plus la raison d'être des catégories d'expression. Dans la machine abstraite (plus d'explications ci-dessous), chaque objet temporaire (y compris celui créé par 3 Dans x = 3;) Existe en mémoire.

Comme décrit précédemment dans ma réponse, un programme se compose d'expressions qui manipulent des objets. Chaque expression est censée désigner ou se référer à un objet.

Il est très courant que d'autres réponses ou articles sur ce sujet prétendent incorrectement qu'une rvalue ne peut désigner qu'un objet temporaire, ou pire encore, qu'une rvalue est un objet temporaire, ou qu'un l'objet temporaire est une valeur r. Une expression n'est pas un objet, c'est quelque chose qui se produit dans le code source pour manipuler des objets!

En fait, un objet temporaire peut être désigné par une expression lvalue ou rvalue; et un objet non temporaire peut être désigné par une expression lvalue ou rvalue. Ce sont des concepts distincts.

Maintenant, il existe une règle de catégorie d'expression que vous ne pouvez pas appliquer & À une expression de la catégorie rvalue. Le but de cette règle et de ces catégories est d'éviter les erreurs lorsqu'un objet temporaire est utilisé après sa destruction. Par exemple:

int *p = &5;    // not allowed due to category rules
*p = 6;         // oops, dangling pointer

Mais vous pouvez contourner cela:

template<typename T> auto f(T&&t) -> T& { return t; }
// ...
int *p = f(5); // Allowed
*p = 6;        // Oops, dangling pointer, no compiler error message.

Dans ce dernier code, f(5) et *p Sont les deux valeurs l qui désignent un objet temporaire. C'est un bon exemple de la raison pour laquelle les règles de catégorie d'expression existent; en suivant les règles sans solution de contournement délicate, nous obtiendrions une erreur pour le code qui essaie d'écrire via un pointeur suspendu.

Notez que vous pouvez également utiliser ce f pour trouver l'adresse mémoire d'un objet temporaire, par exemple std::cout << &f(5);


En résumé, les questions que vous posez réellement confondent toutes les expressions par erreur avec des objets. Ce ne sont donc pas des questions dans ce sens. Les temporaires ne sont pas des valeurs, car les objets ne sont pas des expressions.

Une question valide mais connexe serait: "Pourquoi l'expression qui crée un objet temporaire est-elle une rvalue (par opposition à une lvalue?)"

À laquelle la réponse est comme cela a été discuté ci-dessus: le fait d'avoir une valeur l augmenterait le risque de créer des pointeurs ou des références pendantes; et comme dans 3 = 5;, augmenterait le risque de spécifier des opérations redondantes que le programmeur n'avait probablement pas l'intention.

Je répète à nouveau que les catégories d'expression sont une décision de conception pour aider à l'expressivité du programmeur; rien à voir avec la mémoire ou les emplacements de stockage.


Enfin, à la machine abstraite et à la règle comme si. Le C++ est défini en termes de machine abstraite, dans laquelle les objets temporaires ont également du stockage et des adresses. J'ai donné un exemple plus tôt de la façon d'imprimer l'adresse d'un objet temporaire.

La règle comme si indique que la sortie de l'exécutable réel que le compilateur produit ne doit correspondre qu'à la sortie que la machine abstraite ferait. L'exécutable ne doit pas réellement fonctionner de la même manière que la machine abstraite, il doit juste produire le même résultat.

Donc, pour du code comme x = 5;, Même si un objet temporaire de valeur 5 A un emplacement mémoire dans la machine abstraite; le compilateur n'a pas à allouer de stockage physique sur la vraie machine. Il doit seulement s'assurer que x finit par y avoir 5 Et il existe des moyens beaucoup plus faciles de le faire qui n'impliquent pas de stockage supplémentaire en cours de création.

La règle comme si s'applique à tout dans le programme, même si mon exemple ne se réfère ici qu'aux objets temporaires. Un objet non temporaire pourrait également être optimisé, par exemple int x; int y = 5; x = y; // other code that doesn't use y Pourrait être remplacé par int x = 5;.

Il en va de même pour les types de classe sans effets secondaires qui altéreraient la sortie du programme. Par exemple. std::string x = "foo"; std::cout << x; Peut être optimisé en std::cout << "foo"; Même si la lvalue x désigne un objet avec stockage dans la machine abstraite.

7
M.M

lvalue représente la valeur du localisateur et représente un objet qui occupe un emplacement identifiable en mémoire.

Le terme valeur de localisateur est également utilisé ici :

C

Le langage de programmation C a suivi une taxonomie similaire, sauf que le rôle de l'affectation n'était plus significatif: les expressions C sont classées entre "expressions lvalue" et autres (fonctions et valeurs non-objet), où "lvalue" signifie une expression qui identifie un objet, une "valeur de localisation" [4].

Tout ce qui n'est pas un lvalue est par exclusion un rvalue. Chaque expression est soit un lavalue ou rvalue.

À l'origine, le terme lvalue était utilisé en C pour indiquer des valeurs pouvant rester sur le côté gauche de l'opérateur d'affectation. Cependant, avec le clavier const, cela a changé. Tous les lvalues ne peuvent pas être affectés à. Ceux qui peuvent s'appeler modifiable lvalues.

Et aussi que les variables littérales et temporelles ne sont pas des valeurs l, mais aucune raison n'est donnée pour cette déclaration.

Selon cette réponse les littéraux peuvent être lvalues dans certains cas.

  • les littéraux de types scalaires sont rvalue car ils sont de taille connue et sont très susceptibles d'être intégrés directement dans les commandes de la machine sur le donné architecture matérielle. Quel serait l'emplacement mémoire de 5?
  • Au contraire, étrangement, les littéraux de chaîne sont lvalues car ils ont une taille imprévisible et il n'y a pas d'autre moyen de les représenter en dehors de comme objets en mémoire.

Un lvalue peut être converti en un rvalue. Par exemple dans les instructions suivantes

int a =5;
int b = 3;
int c = a+b;

l'opérateur + prend deux rvalues. Ainsi a et b sont convertis en rvalues avant d'être additionnés. Un autre exemple de conversion:

int c = 6;
&c = 4; //ERROR: &c is an rvalue

Au contraire vous ne pouvez pas convertir un rvalue en un lvalue.

Cependant, vous pouvez produire un lvalue valide à partir d'un rvalue par exemple:

int arr[] = {1, 2};
int* p = &arr[0];
*(p + 1) = 10;   // OK: p + 1 is an rvalue, but *(p + 1) is an lvalue

Dans C++ 11, les références rvalues ​​sont liées au constructeur de déplacement et à l'opérateur d'affectation de déplacement.

Vous pouvez trouver plus de détails dans ce message clair et bien expliqué .

4
Francesco Boi