Кто-нибудь объяснит левое значение и правое значение на уровне языка ассемблера?

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

mov eax,dword ptr [i]
sub eax,1
mov dword ptr [i],eax

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

Таким образом, я могу гарантировать, что --i вернет значение типа, отличного от void, а i-- вернет значение void или, возможно, временную переменную.

Однако, когда я даю задание, такое как i--=5, компилятор выдает мне ошибку, указывающую, что i-- не является lvalue, я не знаю, почему это не так и почему возвращаемое значение является временной переменной. Как компилятор делает такое суждение? Может ли кто-нибудь дать мне какое-то объяснение на уровне языка ассемблера? Спасибо!


person jaurung    schedule 21.12.2012    source источник
comment
lvalue и rvalue обозначают значение загрузки и значение чтения. Не влево и вправо.   -  person user93353    schedule 21.12.2012
comment
разве i--= 5 не двусмысленно? Это может означать i-- = 5 или i- -= 5   -  person ApproachingDarknessFish    schedule 21.12.2012
comment
это не двусмысленно. если это означает i- -= 5, как объяснить эту часть i-?   -  person jaurung    schedule 21.12.2012
comment
@ user93353: Согласно C 1999 6.3.2.1, примечание 54, «lvalue» возникает в левой части выражения и используется для поиска объекта. Особый интерес представляет сохранение в объект (а не загрузка из него). Вот что отличает lvalue: его можно использовать для изменения объекта, а не только для извлечения значения.   -  person Eric Postpischil    schedule 21.12.2012
comment
@ValekHalfHeart: синтаксический анализатор языка следует принципу максимального Мунка (en.wikipedia.org/wiki/Maximal_munch). Требуется жевать как можно больше слева направо до тех пор (и только до тех пор), пока результирующий токен действителен. При синтаксическом анализе i--=5 он будет жевать i в качестве первого токена. И затем, поскольку в C и C++ есть такой допустимый токен, как --, требуется прожевать весь --. Не разрешается останавливаться после первого -. По этой причине он будет интерпретировать i--=5 как i -- = 5, а не как i - -= 5.   -  person AnT    schedule 21.12.2012


Ответы (3)


Левое значение? Правильное значение?

Если вы говорите о lvalues и rvalues, то свойство быть lvalue или rvalue применяется к результату выражения, что означает, что у вас есть рассмотреть результаты --i и i--. А в языке C оба --i и i-- являются rvalue. Итак, ваш вопрос основан на неверной предпосылке в области языка C. --i не является lvalue в C. Я не знаю, что вы пытаетесь сделать, ссылаясь на стандарт C99, поскольку в нем четко указано, что ни одно из них не является lvalue. Кроме того, неясно, что вы подразумеваете под i--, возвращающим void. Нет, встроенный постфикс -- никогда не возвращает void.

Различие lvalue и rvalue в случае --i и i-- существует только в C++.

В любом случае, если вы смотрите на простые операторы выражений --i; и i--;, вы не используете результаты этих выражений. Вы отбрасываете их. Единственная причина использовать автономные --i и i-- — это их побочные эффекты (уменьшение i). Но поскольку их побочные эффекты идентичны, вполне ожидаемо, что сгенерированный код будет таким же.

Если вы хотите увидеть разницу между выражениями --i и i--, вы должны использовать их результаты. Например

int a = --i;
int b = i--;

будет генерировать другой код для каждой инициализации.

Однако этот пример не имеет ничего общего с ценностью или ценностью их результатов. Если вы хотите увидеть разницу с той стороны (которая существует только в C++, как я сказал выше), вы можете попробовать это

int *a = &--i;
int *b = &i--;

Первая инициализация будет компилироваться в C++ (поскольку результатом является lvalue), а вторая не будет компилироваться (поскольку результатом является rvalue, и вы не можете применить встроенный унарный & к rvalue).

Обоснование этой спецификации довольно очевидно. Поскольку --i оценивается как новое значение i, вполне возможно заставить этот оператор возвращать ссылку на само i в качестве своего результата (а язык C++, в отличие от C, предпочитает возвращать lvalues при любой возможности). Между тем, i-- требуется для возврата старого значения i. Поскольку к тому времени, когда мы приступим к анализу результата oh i--, само i, скорее всего, будет содержать новое значение, мы не можем вернуть ссылку на i. Мы должны сохранить (или воссоздать) старое значение i в каком-то вспомогательном временном месте и вернуть его как результат i--. Это временное значение является просто значением, а не объектом. Ему не нужно находиться в памяти, поэтому он не может быть lvalue.

person AnT    schedule 21.12.2012

[Примечание: я отвечаю на это с точки зрения С++.]

Предполагая, что i является встроенным типом, если вы просто пишете --i; или i--;, а не, скажем, j = ++i; или j = i++;, то неудивительно, что компилятор компилирует их в ассемблерный код - они делают то же самое, что и уменьшение i. Разница становится очевидной только на уровне сборки, когда вы что-то делаете с результатом, в противном случае они фактически имеют одинаковую семантику.

(Обратите внимание, что если бы мы думали о перегруженных операторах до и после декремента для определяемого пользователем типа, сгенерированный код был бы другим.)

Когда вы пишете что-то вроде i-- = 5;, компилятор совершенно справедливо жалуется, потому что семантика пост-декремента, по сути, состоит в том, чтобы уменьшить рассматриваемую вещь, но вернуть ее старое значение для дальнейшего использования. Возвращаемая вещь будет временной, поэтому i-- дает r-значение.

person Stuart Golodetz    schedule 21.12.2012
comment
Спасибо за ваш ответ.i--это r-значение, это правило компиляции языка С++, верно?;) - person jaurung; 21.12.2012
comment
@jaurung: язык указывает, что i-- в данном случае является r-значением, да. Компилятор просто обеспечивает это. - person Stuart Golodetz; 21.12.2012

Термины «lvalue» и «rvalue» происходят от выражения присваивания E1 = E2, в котором левый операнд E1 используется для идентификации изменяемого объекта, а правый операнд E2 определяет используемое значение. (См. C 1999 6.3.2.1, примечание 53.)

Таким образом, выражение, с которым все еще связан некоторый объект, может быть использовано для поиска этого объекта и записи в него. Это lvalue. Если выражение не является lvalue, его можно назвать rvalue.

Например, если у вас есть i, имя какого-то объекта, это lvalue, потому что мы можем найти, где находится i, и мы можем присвоить ему значение, как в i = 3.

С другой стороны, если у нас есть выражение i+1, то мы взяли значение i и добавили 1, и теперь у нас есть значение, но оно не связано с конкретным объектом. Это новое значение отсутствует в i. Это просто временное значение и не имеет определенного местоположения. (Конечно, компилятор должен поместить его куда-нибудь, если только оптимизация не удалит выражение полностью. Но оно может быть в регистрах и никогда в памяти. Даже если оно по какой-то причине находится в памяти, язык C не предоставляет вам способа чтобы узнать, где.) Таким образом, i+1 не является lvalue, потому что вы не можете использовать его в левой части присваивания.

--i и i++ являются выражениями, которые являются результатом получения значения i и выполнения некоторых арифметических действий. (Эти выражения также изменяют i, но это побочный эффект оператора, а не часть возвращаемого им результата.) «Левое» и «правое» значений lvalue и rvalue не имеют ничего общего с тем, включен ли оператор -- или ++. левая или правая сторона имени; они имеют отношение к левой или правой стороне задания. Как объясняют другие ответы, в С++, когда они находятся слева от lvalue, они возвращают lvalue. Однако это случайно; это определение операторов в C++ появилось спустя много лет после создания термина «lvalue».

person Eric Postpischil    schedule 21.12.2012