Почему c = ++ (a + b) дает ошибку компиляции?

111

После исследования я прочитал, что оператор инкремента требует, чтобы операнд имел изменяемый объект данных: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

Исходя из этого, я предполагаю, что это дает ошибку компиляции, потому что (a+b)это временное целое число, поэтому его нельзя изменить.

Это понимание правильное? Это был мой первый раз, когда я пытался исследовать проблему, поэтому, если я должен был что-то искать, пожалуйста, сообщите.

DNG
источник
35
Это неплохо с точки зрения исследования. Ты на правильном пути.
StoryTeller - Unslander Monica
35
Что вы ожидаете от выражения?
qrdl
4
в соответствии со стандартом C11 6.5.3.1: операнд оператора приращения или декремента префикса должен иметь атомарный, квалифицированный или неквалифицированный вещественный или указательный тип и должен быть изменяемым lvalue
Кристиан Гиббонс
10
Как бы вы хотели, чтобы 1 распределялась между a и b? «Должны ли индексы массива начинаться с 0 или 1? Мой компромисс 0,5 был отклонен, как я думал, без должного рассмотрения». - Стэн Келли-Бутл
Эндрю Мортон
5
Я думаю, что следующий вопрос заключается в том, зачем вам вообще это нужно, когда c = a + b + 1ваше намерение становится более ясным, а также короче для ввода. Операторы увеличения / уменьшения делают две вещи: 1. они и их аргумент формируют выражение (которое может использоваться, например, в цикле for), 2. они изменяют аргумент. В вашем примере вы используете свойство 1., но не свойство 2., поскольку вы выбрасываете измененный аргумент. Если вам не нужно свойство 2. и нужно просто выражение, то вы можете просто написать выражение, например x + 1 вместо x ++.
Trevor

Ответы:

117

Это просто правило, вот и все, и, возможно, оно существует для (1) упрощения написания компиляторов C и (2) никто не убедил комитет по стандартам C ослабить его.

Неформально говоря, вы можете писать, только ++fooесли они fooмогут появиться в левой части выражения присваивания, например foo = bar. Поскольку вы не можете писать a + b = bar, вы не можете и писать ++(a + b).

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

Вирсавия
источник
4
Я думаю, что пункт (1) попадает в самую точку. Простой взгляд на правила временной материализации в C ++ может перевернуть желудок (но это мощно, надо сказать).
StoryTeller - Unslander Monica
4
@StoryTeller: Действительно, в отличие от нашего любимого языка C ++, C по-прежнему компилируется в сборку относительно тривиально.
Вирсавия
29
Вот настоящая причина ИМХО: это было бы ужасной путаницей, если бы ++иногда имелся побочный эффект изменения чего-либо, а иногда просто нет.
aschepler
5
@dng: Да, это так; вот почему были введены термины lvalue и rvalue, хотя сейчас все гораздо сложнее (особенно в C ++). Например, константа никогда не может быть lvalue: что-то вроде 5 = a не имеет смысла.
Вирсавия
6
@Bathsheba Это объясняет, почему 5 ++ также вызывает ошибку компиляции
dng
40

Стандарт C11 гласит в разделе 6.5.3.1.

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

А "изменяемое lvalue" описано в разделе 6.3.2.1, подраздел 1.

Lvalue - это выражение (с типом объекта, отличным от void), которое потенциально обозначает объект; если lvalue не обозначает объект при его оценке, поведение не определено. Когда говорится, что объект имеет определенный тип, тип определяется значением lvalue, используемым для обозначения объекта. Модифицируемое lvalue - это lvalue, которое не имеет типа массива, не имеет неполного типа, не имеет константного типа и, если это структура или объединение, не имеет какого-либо члена (включая, рекурсивно, любой член или элемент всех содержащихся агрегатов или объединений) с типом, квалифицированным как const.

So (a+b)не является изменяемым lvalue и поэтому не подходит для оператора увеличения префикса.

Кристиан Гиббонс
источник
1
Ваш вывод из этих определений отсутствует ... Вы хотите сказать, что (a + b) потенциально не обозначает объект, но эти абзацы не позволяют этого.
hkBst
21

Ты прав. ++пытается присвоить новое значение исходной переменной. Итак ++a, возьмем значение a, добавим 1к нему, а затем вернем его a. Поскольку, как вы сказали, (a + b) является временным значением, а не переменной с назначенным адресом памяти, назначение не может быть выполнено.

Рои Гавирель
источник
12

Я думаю, вы в основном ответили на свой вопрос. Я мог бы внести небольшое изменение в вашу формулировку и заменить «временную переменную» на «rvalue», как упомянул К. Гиббонс.

Термины переменная, аргумент, временная переменная и т. Д. Станут более понятными по мере того, как вы узнаете о модели памяти C (это выглядит как хороший обзор: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

Термин «rvalue» может показаться непонятным, когда вы только начинаете, поэтому я надеюсь, что следующее поможет вам развить интуицию по этому поводу.

Lvalue / rvalue говорят о разных сторонах знака равенства (оператор присваивания): lvalue = левая сторона (строчная L, а не «единица») rvalue = правая сторона

Немного узнав, как C использует память (и регистры), будет полезно понять, почему это различие важно. В общих чертах компилятор создает список инструкций машинного языка, которые вычисляют результат выражения (rvalue), а затем помещает этот результат куда-нибудь (lvalue). Представьте компилятор, имеющий дело со следующим фрагментом кода:

x = y * 3

В псевдокоде ассемблера это может выглядеть примерно так:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

Оператору ++ (и его аналогу) требуется «где-то» для изменения, по существу все, что может работать как lvalue.

Понимание модели памяти C будет полезно, потому что вы получите лучшее представление о том, как аргументы передаются функциям и (в конечном итоге) как работать с динамическим распределением памяти, например с функцией malloc (). По тем же причинам вы можете в какой-то момент изучить простое программирование на ассемблере, чтобы лучше понять, что делает компилятор. Также, если вы используете gcc , опция -S «Остановить после стадии собственно компиляции; не собирать». может быть интересным (хотя я бы рекомендовал попробовать его на небольшом фрагменте кода).

Просто в сторону: инструкция ++ существует с 1969 года (хотя она началась в предшественнике C, B):

(Кен Томпсон) заметил, что перевод ++ x был меньше, чем перевод x = x + 1 ».

Следуя этой ссылке в Википедии, вы перейдете к интересной статье Денниса Ричи («R» в «K&R C») по истории языка C, ссылка на которую для удобства приведена здесь: http://www.bell-labs.com/ usr / dmr / www / chist.html, где вы можете искать "++".

Jgreve
источник
6

Причина в том, что стандарт требует, чтобы операнд был lvalue. Выражение (a+b)не является lvalue, поэтому применение оператора увеличения не допускается.

Теперь, можно сказать : «Хорошо, что это на самом деле причина, но на самом деле нет * реального * причины кроме этого» , но несчастию конкретной формулировки того , как оператор работает фактически делает требовать, чтобы быть.

Выражение ++ E эквивалентно (E + = 1).

Очевидно, вы не можете писать, E += 1если Eне lvalue. Это позор, потому что с таким же успехом можно было сказать: «увеличивает E на единицу» и готово. В этом случае применение оператора к значению, отличному от lvalue, было бы (в принципе) совершенно возможным за счет немного усложнения компилятора.

Теперь определение можно было бы тривиально перефразировать (я думаю, что это даже не изначально C, а семейная реликвия B), но это в корне изменило бы язык на то, что больше не совместимо с его предыдущими версиями. Поскольку возможная выгода довольно мала, но возможные последствия огромны, этого никогда не было и, вероятно, никогда не произойдет.

Если вы рассмотрите C ++ в дополнение к C (вопрос помечен как C, но обсуждались перегрузки операторов), история становится еще более сложной. В C трудно представить, что это могло быть так, но в C ++ результатом (a+b)вполне может быть что-то, что вы вообще не можете увеличить, или увеличение может иметь очень значительные побочные эффекты (а не просто добавление 1). Компилятор должен уметь справляться с этим и диагностировать проблемные случаи по мере их возникновения. При lvalue это все еще тривиально проверить. Но не в отношении любого случайного выражения в скобках, которое вы бросаете бедняге.
Это не настоящая причина, по которой он не мог быть сделано, но это, несомненно, служит объяснением того, почему люди, которые реализовали это, не совсем в восторге от добавления такой функции, которая обещает очень мало пользы для очень немногих людей.

Дэймон
источник
3

(a + b) оценивает значение r, которое не может быть увеличено.

Каспер Б. Хансен
источник
3

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

Бабу Чандермани
источник
2

Когда выполняется выражение ++ (a + b), тогда, например:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
Джит Парих
источник