Почему литералы и временные переменные не являются значениями? - PullRequest
0 голосов
/ 12 февраля 2019

Я читал, что lvalues - это «вещи с определенным местом хранения».

А также, что переменные литералов и временных переменных не являются lvalues, но причина для этого утверждения не приводится.

Это потому, что литералы и временные переменные не имеют определенного места хранения?Если да, то где они находятся, если не в памяти?

Я полагаю, что в "определенном месте хранения" есть какое-то значение для "определенного", если есть (или нет), пожалуйста, дайте мне знать.

Ответы [ 5 ]

0 голосов
/ 13 февраля 2019

Есть много распространенных заблуждений в вопросе и других ответах;мой ответ надеется решить эту проблему.

Термины lvalue и rvalue являются категориями выражений .Это термины, которые применяются к выражениям.Не к объектам.(Немного смущает, официальный термин для категорий выражений - «категории значений»!)

Термин временный объект относится к объектам.Это включает в себя объекты типа класса, а также объекты встроенного типа.Термин временный (используется как существительное) является сокращением от временный объект .Иногда автономный термин значение используется для обозначения временного объекта встроенного типа.Эти термины применяются к объектам, а не к выражениям.

Стандарт C ++ 17 более согласован в терминологии объектов, чем предыдущие стандарты, например, см. [Conv.rval] / 1.Теперь он пытается не произносить значение , кроме как в контексте значение выражения .


Теперь, почему существуют разные категории выражений?Программа на C ++ состоит из набора выражений, соединенных друг с другом операторами для создания больших выражений;и вписывание в рамки декларативных конструкций.Эти выражения создают, уничтожают и делают другие манипуляции с объектами.Программирование на C ++ можно описать как использование выражений для выполнения операций с объектами.

Причина, по которой существуют категории выражений, заключается в предоставлении основы для использования выражений для выражения операций, которые программист намеревается.Например, еще в дни Си (и, возможно, раньше) разработчики языка полагали, что 3 = 5; не имеет никакого смысла как часть программы, поэтому было решено ограничить то, какое выражение может появляться в левой части.=, и пусть компилятор сообщит об ошибке, если это ограничение не соблюдалось.

Термин lvalue возник в те дни, хотя сейчас с развитием C ++ существуетширокий спектр выражений и контекстов, в которых полезны категории выражений, а не только левая часть оператора присваивания.

Вот правильный код C ++: std::string("3") = std::string("5");.Концептуально это ничем не отличается от 3 = 5;, однако допускается.В результате создается временный объект типа std::string и содержимое "3", затем этот временный объект изменяется на содержимое "5", а затем временный объект уничтожается.Язык мог бы быть спроектирован так, чтобы код 3 = 5; определял подобную серию событий (но это не так).


Почему пример string допустим, а пример intне?

Каждое выражение должно иметь категорию.На первый взгляд категория выражения может не иметь очевидной причины, но разработчики языка дали каждому выражению категорию в соответствии с тем, что, по их мнению, является полезным понятием для выражения, а что - нет.

Было решено, что последовательность событий в 3 = 5;, как описано выше, не является чем-то, что кто-то хотел бы сделать, и если кто-то написал такую ​​вещь, он, вероятно, допустил ошибку и имел в виду что-то другое, поэтому компилятор должен помочьсообщая об ошибке.

Теперь та же логика может заключить, что std::string("3") = std::string("5") - это не то, что кто-либо когда-либо захочет сделать.Однако другой аргумент заключается в том, что для некоторого другого типа класса T(foo) = x; может быть полезной операцией, например, потому что T может иметь деструктор, который что-то делает.Было решено, что запрет такого использования может быть более вредным для намерений программиста, чем пользой.(Независимо от того, что это было хорошее решение или не является спорным; см этот вопрос для обсуждения)


Теперь мы приближаемся, наконец, ответ на свой вопрос:.)

Наличие или отсутствие памяти или места хранения больше не является обоснованием для категорий выражений.В абстрактной машине (более подробное объяснение этого ниже) каждый временный объект (включая объект, созданный 3 в x = 3;) существует в памяти.

Как описано ранее вмой ответ, программа состоит из выражений, которые манипулируют объектами.Каждое выражение называется обозначает или относится к объекту.

В других ответах или статьях по этой теме очень часто делается неверное утверждение, что rvalue может обозначать только временный объект или, что еще хуже, rvalue является временным объектом,или что временный объект является значением.Выражение не является объектом, это то, что происходит в исходном коде для манипулирования объектами!

Фактически временный объект может быть обозначен выражением lvalue или rvalue;и невременный объект может быть обозначен lvalue или выражением rvalue.Это отдельные понятия.

Теперь есть правило категории выражений, которое нельзя применять & к выражению категории rvalue.Цель этого правила и этих категорий состоит в том, чтобы избежать ошибок, когда временный объект используется после его уничтожения.Например:

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

Но вы можете обойти это:

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

В этом последнем коде f(5) и *p оба являются lvalue, которые обозначают временный объект.Это хороший пример того, почему существуют правила категории выражений;следуя правилам без хитрого обходного пути, мы получим ошибку для кода, который пытается написать через висячий указатель.

Обратите внимание, что вы также можете использовать этот f, чтобы найти адрес памятивременный объект, например, std::cout << &f(5);


Таким образом, все вопросы, которые вы фактически задаете, ошибочно связывают выражения с объектами.Таким образом, они не являются вопросами в этом смысле.Временные значения не являются lvalue, потому что объекты не являются выражениями.

Допустимый, но связанный с этим вопрос: «Почему выражение, создающее временный объект, является rvalue (а не lvalue?)»

Ответ на который был, как обсуждалось выше: наличие lvalue увеличило бы риск создания висячих указателей или висячих ссылок;и, как в 3 = 5;, увеличился бы риск указания избыточных операций, которые программист, вероятно, не намеревался.

Еще раз повторяю, что категории выражений - это дизайнерское решение, помогающее программистам выразиться;не имеет ничего общего с памятью или местами хранения.


Наконец, к абстрактной машине и как-будто правилу .C ++ определяется в терминах абстрактной машины, в которой временные объекты также имеют память и адреса.Ранее я привел пример того, как напечатать адрес временного объекта.

Правило as-if гласит, что выходные данные фактического исполняемого файла, создаваемого компилятором, должны совпадать только с выходными данными абстрактной машины.На самом деле исполняемый файл не должен работать так же, как абстрактная машина, он просто должен давать тот же результат.

То есть для кода, подобного x = 5;, даже если временный объект со значением 5 имеет место в памяти в абстрактной машине;компилятору не нужно выделять физическую память на реальной машине.Нужно только убедиться, что x в конечном итоге хранит 5 в нем, и есть гораздо более простые способы сделать это, не включая создание дополнительного хранилища.

как-будтоПравило применяется ко всему в программе, хотя мой пример здесь относится только к временным объектам.Невременный объект также можно оптимизировать, например, int x; int y = 5; x = y; // other code that doesn't use y можно изменить на int x = 5;.

То же самое относится к типам классов без побочных эффектов, которые могли бы изменить вывод программы.Например, std::string x = "foo"; std::cout << x; можно оптимизировать до std::cout << "foo";, хотя lvalue x обозначает объект с хранилищем в абстрактной машине.

0 голосов
/ 12 февраля 2019

lvalue обозначает значение локатора и представляет объект, который занимает определенное место в памяти.

Термин значение локатора также используется здесь :

C

Язык программирования C следовал аналогичномуТаксономия, за исключением того, что роль присваивания больше не была существенной: выражения C подразделяются на «выражения lvalue» и другие (функции и значения не-объекта), где «lvalue» означает выражение, которое идентифицирует объект, «значение локатора»[4].

Все, что не является lvalue, является исключением rvalue.Каждое выражение является либо lavalue, либо rvalue.

Изначально термин C lvalue использовался в C для обозначения значений, которые могут оставаться слева от оператора присваивания.Однако с клавиатурой const это изменилось.Не все lvalues могут быть назначены.Те, которые могут называться modifiable lvalues.

А также, что переменные литералов и временных переменных не являются lvalues, но для этого утверждения не указана причина.

Согласно этот ответ литералы могутбыть lvalues в некоторых случаях.

  • литералы скалярных типов равны rvalue, поскольку они имеют известный размер и с большой вероятностью могут быть встроены непосредственно в машинные команды на данной аппаратной архитектуре.Какова будет область памяти 5?
  • Наоборот, как ни странно, строковые литералы равны lvalues, так как они имеют непредсказуемый размер, и другого способа представить их нет.кроме объектов в памяти.

lvalue можно преобразовать в rvalue.Например, в следующих инструкциях

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

оператор + принимает два rvalues.Таким образом, a и b преобразуются в rvalues перед суммированием.Другой пример преобразования:

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

Наоборот вы не можете преобразовать rvalue в lvalue.

Однако вы можете создать действительный lvalue из rvalue, например:

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

В C ++ 11 ссылки на значения связаны с конструктором перемещения и оператором присваивания перемещения.

Вы можетеБолее подробную информацию можно найти в этом четком и понятном сообщении .

0 голосов
/ 12 февраля 2019

А также, что переменные литералов и временных переменных не являются lvalues, но для этого утверждения не указана причина.

Это верно для всех временных и литералов, кроме строковых литералов.На самом деле это lvalues ​​(что объясняется ниже).

Это потому, что переменные литералов и временных переменных не имеют определенного места хранения?Если да, то где они находятся, если не в памяти?

Да.Буквально 2 на самом деле не существует;это просто значение в исходном коде.Так как это значение, а не объект, ему не нужно иметь какую-либо память, связанную с ним.Он может быть жестко запрограммирован в сборку, которую создает компилятор, или он может быть помещен куда-то, но так как этого не требуется, все, что вы можете сделать, это рассматривать его как чистое значение, а не как объект.

Хотя есть исключение, и это строковые литералы.У них на самом деле есть память, поскольку строковый литерал представляет собой массив const char[N].Вы можете взять адрес строкового литерала, а строковый литерал может распасться на указатель, так что это lvalue, даже если у него нет имени.

Временные значения также являются rvalues.Даже если они существуют как объекты, их место хранения является эфемерным.Они действуют только до конца полного выражения, в котором они находятся. Вам не разрешено брать их адрес, и у них также нет имени.Они могут даже не существовать: например, в

Foo a = Foo();

Foo() можно удалить, а код семантически преобразовать в

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

, так что теперь нет даже временногообъект в оптимизированном коде.

0 голосов
/ 12 февраля 2019

Почему литералы и временные переменные не являются lvalues?

У меня есть два ответа: потому что это не имеет смысла (1) и потому, что Стандарт говорит так (2).Давайте сосредоточимся на (1).

Это потому, что переменные литералов и временных переменных не имеют определенного места хранения?

Это упрощение, которое здесь не подходит.Упрощение: литералы и временные не являются lvalues, потому что не имеет смысла изменять их 1 .

Что означает 5++?Что означает rand() = 0? Стандарт говорит, что временные и литеральные значения не являются lvalues, поэтому эти примеры являются недействительными.И каждый разработчик компилятора счастливее.


1) Вы можете определять и использовать пользовательские типы таким образом, чтобы модификация временного кода имела смысл.Этот временный дожил бы до оценки полного выражения.Франсуа Андрие проводит хорошую аналогию между вызовом f(MyType{}.mutate()) с одной стороны и f(my_int + 1) с другой.Я думаю, что упрощение сохраняется, поскольку MyType{}.mutate() может быть , видимым как , другим временным, как MyType{}, как my_int + 1, может быть , видимым как , другим int как my_intбыло.Это все семантика и мнения.Реальный ответ: (2) потому что Стандарт говорит так.

0 голосов
/ 12 февраля 2019

Где они находятся, если не в памяти?

Конечно, они находятся в памяти *, нет никакого способа обойти это.Вопрос в том, может ли ваша программа определить, где именно в памяти они находятся.Другими словами, разрешено ли вашей программе принимать адрес рассматриваемой вещи.

В простом примере a = 5 значение пять или инструкция, представляющая присвоение значения пять, где-тов памяти.Однако вы не можете взять адрес пять, потому что int *p = &5 недопустимо.

Обратите внимание, что строковые литералы являются исключением из правила "not lvalue", поскольку const char *p = "hello" создает адрес строкового литерала.

* Однако это не обязательно может быть data memory.Фактически, они могут даже не быть представлены как константа в памяти программы: например, присвоение short a; a = 0xFF00 может быть представлено как присвоение 0xFF в верхнем октете и очистка нижнего октета в памяти.

...