ПрограммированиеC-разработчик, Embedded engineer

Что такое lvalue и rvalue в языке C, как их различать и в чём практическое значение этого различия?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Исторически в языке C различие между lvalue и rvalue возникло как основа для определения того, к каким выражениям можно применять оператор присваивания. Lvalue (left value) обозначает объект, который занимает определённое место в памяти и к которому можно применить операцию взятия адреса (&). Rvalue (right value) — это временное значение, не имеющее определённого адреса. Это различие важно для понимания, как работает присваивание, передача аргументов в функцию и оптимизация компилятором.

Проблема возникает при попытке выполнять операции, допустимые только над lvalue (например, присваивание), для rvalue, и наоборот. Это может приводить к ошибкам компиляции или к неочевидным багам уже на этапе выполнения.

Решение заключается в чёткого понимания контекстов использования lvalue и rvalue. Пример:

int x = 5; int y; y = x; // x — lvalue и rvalue, y — lvalue y = x + 1; // x + 1 — rvalue (нельзя взять адрес) // &x — корректно, &(x + 1) — ошибка

Ключевые особенности:

  • Lvalue имеет адрес в памяти и может быть левым операндом присваивания.
  • Rvalue — временное значение, не имеющее своего адреса.
  • Некоторые выражения могут быть и lvalue, и rvalue, в зависимости от контекста.

Вопросы с подвохом.

Можно ли взять адрес от любого выражения, например от выражения (x + y)?

Нет, только lvalue имеет адрес. Например, выражение (x + y) — это rvalue.

int z = 3, y = 7; int *p = &(z + y); // Ошибка компиляции

Что произойдет при попытке присвоить значение константе (например, 5 = x)?

Произойдет ошибка компиляции, потому что литерал 5 — rvalue и не может выступать в качестве lvalue.

5 = x; // Ошибка: левый операнд не является lvalue

Может ли функция возвращать lvalue?

Обычная функция возвращает rvalue, но если возвращается ссылка (например, в C++), то это lvalue. В C допустимы только возвраты rvalue.

int foo() { int x = 5; return x; } // Возвращается rvalue

Типовые ошибки и анти-паттерны

  • Использование оператора взятия адреса над выражениями, которые не являются lvalue.
  • Попытка присваивания rvalue, например, литералу или результату арифметического выражения.
  • Ожидание того, что все выражения имеют адрес в памяти.

Пример из жизни

Негативный кейс

Программист попытался взять адрес у временного результата выражения (a + b):

int *p = &(a + b);

Плюсы:

  • Ошибка компиляции сразу укажет на проблему.

Минусы:

  • Нарушение логики программы, если не понимать различие lvalue и rvalue, приведет к долгому поиску и непонятным ошибкам.

Позитивный кейс

Программист осознанно отделяет lvalue и rvalue. Использует значения только там, где разрешет синтаксис и семантика:

int x = 7; int *p = &x; // корректно

Плюсы:

  • Программа работает предсказуемо и переносимо.

Минусы:

  • Небольшие накладные расходы времени на обучение их различию.