Historiquement, en langage C, la distinction entre lvalue et rvalue est apparue comme une base pour déterminer quelles expressions peuvent utiliser l'opérateur d'affectation. Lvalue (valeur de gauche) désigne un objet qui occupe un emplacement défini en mémoire et auquel on peut appliquer l'opération de prise d'adresse (&). Rvalue (valeur de droite) est une valeur temporaire qui n'a pas d'adresse définie. Cette distinction est importante pour comprendre comment fonctionne l'affectation, le passage d'arguments à une fonction et l'optimisation par le compilateur.
Le problème survient lorsque l'on essaie d'exécuter des opérations qui ne sont autorisées que sur lvalue (par exemple, l'affectation) sur rvalue, et vice versa. Cela peut entraîner des erreurs de compilation ou des bogues difficilement décelables à l'étape d'exécution.
La solution réside dans une compréhension claire des contextes d'utilisation de lvalue et rvalue. Exemple :
int x = 5; int y; y = x; // x — lvalue et rvalue, y — lvalue y = x + 1; // x + 1 — rvalue (adresse ne peut pas être prise) // &x — correct, &(x + 1) — erreur
Caractéristiques clés :
Peut-on prendre l'adresse de n'importe quelle expression, par exemple de l'expression (x + y) ?
Non, seule lvalue a une adresse. Par exemple, l'expression (x + y) — c'est rvalue.
int z = 3, y = 7; int *p = &(z + y); // Erreur de compilation
Que se passe-t-il si l'on essaie d'affecter une valeur à une constante (par exemple, 5 = x) ?
Une erreur de compilation se produira, car le littéral 5 — rvalue et ne peut pas jouer le rôle de lvalue.
5 = x; // Erreur : l'opérande gauche n'est pas lvalue
Une fonction peut-elle renvoyer une lvalue ?
Une fonction ordinaire renvoie une rvalue, mais si une référence est renvoyée (par exemple, en C++), alors c'est lvalue. En C, seules les rvalue peuvent être renvoyées.
int foo() { int x = 5; return x; } // Renvoyer rvalue
Un programmeur a tenté de prendre l'adresse d'un résultat temporaire d'une expression (a + b) :
int *p = &(a + b);
Avantages :
Inconvénients :
Un programmeur sépare consciemment lvalue et rvalue. N'utilise des valeurs que là où la syntaxe et la sémantique le permettent :
int x = 7; int *p = &x; // correct
Avantages :
Inconvénients :