Каков результат + = в C и C ++?

93

У меня есть такой код:

#include <stdio.h>
int main(int argc, char **argv) {
    int i = 0;
    (i+=10)+=10;
    printf("i = %d\n", i);
    return 0;
}

Если я попытаюсь скомпилировать его как источник C с помощью gcc, я получаю сообщение об ошибке:

error: lvalue required as left operand of assignment

Но если я скомпилирую его как источник C ++ с использованием g ++, я не получу ошибки, и когда я запускаю исполняемый файл:

i = 20

Почему другое поведение?

Светлин Младенов
источник
85
Другой язык, разные правила синтаксиса ?. Лично я бы отклонил этот код при проверке кода.
Макс
7
Избегайте такого кода, imo ... Непонятно для всех.
allaire
1
Несомненно, код не чист, и его следует избегать при «реальной» разработке. Но, тем не менее, я наблюдаю такое же поведение и хотел бы узнать его причины.
ulidtko
9
Это НЕ фрагмент кода реального программного обеспечения. Это просто излом, на который я наткнулся случайно.
Светлин Младенов
3
@JohnDibling Я думаю, что проголосовало именно за (i + = 10) + = 10, я не знаю, на каком языке является законный код, и тот факт, что он говорит, что C ++ фактически компилирует его, меня заинтриговал.
Tony318

Ответы:

133

Семантика составных операторов присваивания различна в C и C ++:

Стандарт C99, 6.5.16, часть 3:

Оператор присваивания сохраняет значение в объекте, обозначенном левым операндом. Выражение присваивания имеет значение левого операнда после присваивания, но не является l-значением.

В C ++ 5.17.1:

Оператор присваивания (=) и составные операторы присваивания группируются справа налево. Все требуют модифицируемого lvalue в качестве своего левого операнда и возвращают lvalue с типом и значением левого операнда после того, как присвоение имело место.

EDIT: поведение (i+=10)+=10в C ++ не определено в C ++ 98, но хорошо определено в C ++ 11. См. Этот ответ на вопрос NPE для получения информации о соответствующих частях стандартов.

Сергей Калиниченко
источник
Верный. Один возвращает значение результата, а другой возвращает переменную (адрес)
Техасбрус,
7
Важно : обратите внимание, что (i+=10)+=10в C ++ поведение undefined, см. Ответ @aix.
Дэвид Родригес - дрибес
@ DavidRodríguez-dribeas Вы имели в виду неопределенное , а не неопределенное , верно?
Сергей Калиниченко
4
@dasblinkenlight: Нет, он имел в виду undefined . В C ++ 03 и более ранних версиях изменение результата выражения lvalue ведет себя непредсказуемо во всех компиляторах из-за отсутствия промежуточной точки последовательности. Если бы он не был указан , он бы вел себя предсказуемо, но по-разному на разных компиляторах .
Джастин ᚅᚔᚈᚄᚒᚔ
2
Это было бы полезно при такой настройке, как int f(int &y); f(x += 10);- передача ссылки на измененную переменную в функцию.
Фил Миллер,
51

Помимо недопустимого кода C, строка

(i+=10)+=10;

приведет к неопределенному поведению как в C, так и в C ++ 03, поскольку он будет iдважды изменяться между точками последовательности.

Относительно того, почему разрешено компилировать на C ++:

[C ++ N3242 5.17.1] Оператор присваивания (=) и составные операторы присваивания группируются справа налево. Все требуют изменяемого lvalue в качестве левого операнда и возвращают lvalue, относящееся к левому операнду.

В том же абзаце говорится, что

Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.

Это говорит о том, что в C ++ 11 выражение больше не имеет неопределенного поведения.

NPE
источник
3
Ну, это конечно UB из-за точек последовательности. Это также недопустимый код в C (но не в C ++), но он не связан с точками последовательности и должен быть обнаружен компилятором.
Конрад Рудольф
2
@KonradRudolph: нет, компилятор не обязан улавливать неопределенное поведение, в отличие от плохо сформированного кода. Мы не согласны с тем, что "должен быть пойман компилятором".
2
Точки последовательности не существуют в C ++ 11, поэтому фактическая причина использования UB заключается в том, что есть две модификации, iкоторые не имеют последовательности.
Mankarse
4
Неверно, что это неопределенное поведение. Если бы присваивание не было последовательным перед вычислением значения выражения присваивания, тогда получилось i = j+=1бы неопределенное значение. Из того же абзаца вы цитируете: «Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов, но перед вычислением значения выражения присваивания». Поэтому (i+=10)+=10вполне определено делать i += 10; i += 10;. С другой стороны (i+=10)+=(i+=10)- УБ.
bames53
2
Поскольку я увидел, что по этому поводу есть разногласия между комментаторами, я разместил это как отдельный вопрос: stackoverflow.com/questions/10655290/…
NPE