Разница между C и С++ относительно оператора ++
Я обманывал каким-то кодом и видел что-то, что я не понимаю "почему".
int i = 6;
int j;
int *ptr = &i;
int *ptr1 = &j
j = i++;
//now j == 6 and i == 7. Straightforward.
Что, если вы поместите оператор в левую сторону знака равенства?
++ptr = ptr1;
эквивалентно
(ptr = ptr + 1) = ptr1;
тогда
ptr++ = ptr1;
эквивалентно
ptr = ptr + 1 = ptr1;
Postfix запускает ошибку компиляции, и я получаю ее. У вас есть постоянный "ptr + 1" в левой части оператора присваивания. Справедливо.
Префикс компилируется и РАБОТАЕТ на С++. Да, я понимаю, что это беспорядочно, и вы имеете дело с нераспределенной памятью, но она работает и компилируется. В C это не компилируется, возвращая ту же ошибку, что и постфикс "lvalue, требуемый как левый операнд присвоения". Это происходит независимо от того, как это написано, расширилось с помощью двух операторов "=" или синтаксиса "++ ptr".
В чем разница между тем, как C обрабатывает такое назначение и как С++ обрабатывает его?
Ответы
Ответ 1
В C и С++ результат x++
является значением r, поэтому вы не можете назначить ему.
В C, ++x
эквивалентно x += 1
(стандарт C.5.5.3.1/p2, все стандартные C-ссылки относятся к WG14 N1570). В С++ ++x
эквивалентен x += 1
, если x
не является bool
(стандарт С++ §5.3.2 [expr.pre.incr]/p1; все цитируемые С++ ссылки относятся к WG21 N3936).
В C результатом выражения присваивания является rvalue (стандарт C.6.5.16/p3):
Оператор присваивания сохраняет значение в объекте, обозначенном левый операнд. Выражение присваивания имеет значение слева операнд после назначения, но не является значением l.
Поскольку это не lvalue, вы не можете назначить ему: (C-стандарт §6.5.16/p2 - обратите внимание, что это ограничение)
Оператор присваивания должен иметь модифицируемое значение lvalue как его левое операнд.
В С++ результатом выражения присваивания является lvalue (стандарт С++ §5.17 [expr.ass]/p1):
Оператор присваивания (=) и все операторы присваивания группа справа налево. Все требуют модификации lvalue как их левые операндом и возвратом lvalue, относящимся к левому операнду.
Итак, ++ptr = ptr1;
является диагностическим нарушением ограничения в C, но не нарушает какое-либо диагностируемое правило на С++.
Однако pre-С++ 11, ++ptr = ptr1;
имеет поведение undefined, так как он дважды меняет ptr
между двумя соседними точками последовательности.
В С++ 11 поведение ++ptr = ptr1
становится четким. Это яснее, если мы перепишем его как
(ptr += 1) = ptr1;
Так как С++ 11, стандарт С++ предусматривает, что (§5.17 [expr.ass]/p1)
Во всех случаях назначение упорядочивается после вычисления значения правого и левого операндов и перед вычислением значения выражение присваивания. Что касается неопределенно-секвенированный вызов функции, работа соединения назначение - это единая оценка.
Таким образом, присваивание, выполняемое =
, секвенируется после вычисления значения ptr += 1
и ptr1
. Назначение, выполняемое +=
, секвенируется перед вычислением значения ptr += 1
, и все вычисления значений, требуемые +=
, обязательно должны быть секвенированы до этого назначения. Таким образом, последовательность здесь хорошо определена и не существует поведения undefined.
Ответ 2
В C результатом до и после приращения являются rvalues, и мы не можем назначить rvalue, нам нужно lvalue (также посмотреть: Понимание lvalues и rvalues в C и С++). Мы можем увидеть, перейдя в черновик стандарта C11 6.5.2.4
Операторы приращения и сокращения Postfix, которые говорят (акцент мой вперед):
Результат оператора postfix ++ - это значение. операнд. [...] См. Обсуждения аддитивных операторов и сложных назначение информации о ограничениях, типах и преобразованиях влияние операций на указатели. [...]
Таким образом, результат post-increment - это значение, которое является синонимом для rvalue, и мы можем подтвердить это, перейдя в раздел 6.5.16
Операторы присваивания, которые вышеприведенный параграф указывает нам на дальнейшее понимание ограничений и результатов, в нем говорится:
[...] Выражение присваивания имеет значение левого операнда после назначение , но не lvalue. [...]
который далее подтверждает, что результат пост-приращения не является lvalue.
Для предварительного приращения мы можем видеть из раздела 6.5.3.1
Операторы приращения и уменьшения префиксов, которые гласят:
[...] См. обсуждения аддитивных операторов и составное назначение для информацию об ограничениях, типах, побочных эффектах и конверсиях и эффекты операций над указателями.
также указывает на 6.5.16
, как и пост-приращение, и поэтому результат предварительного приращения в C также не является значением l.
В С++ post-increment также является значением rvalue, более конкретно значением prvalue, которое мы можем подтвердить, перейдя в раздел 5.2.6
Приращение и декремент, в котором говорится:
[...] Результат - это prvalue. Тип результата - cv-unqualified версия типа операнда [...]
По сравнению с предварительным приращением C и С++ отличаются. В C результатом является rvalue, а в С++ результат - это lvalue, который объясняет, почему ++ptr = ptr1;
работает в С++, но не C.
Для С++ это описано в разделе 5.3.2
Приращение и декремент, в котором говорится:
[...] Результатом является обновленный операнд; это lvalue, и это бит-поле, если операндом является бит-поле. [...]
Чтобы понять:
++ptr = ptr1;
хорошо определен или нет в С++, нам нужны два разных подхода: один для pre С++ 11 и один для С++ 11.
Pre С++ 11 это выражение вызывает undefined поведение, так как он изменяет объект более одного раза в одной и той же точке последовательности. Мы можем это увидеть, перейдя в предварительную стандартную секцию Pre С++ 11 5
Expressions, в которой говорится:
За исключением тех случаев, когда отмечено, порядок оценки операндов отдельных операторы и подвыражения отдельных выражений, а также порядок в котором происходят побочные эффекты, неуказан .57) Между предыдущая и следующая точка последовательности скалярный объект должен иметь значение, измененное не более одного раза путем оценки выражения.Кроме того, к предыдущему значению следует обращаться только для определения значение, которое необходимо сохранить. Требования настоящего пункта выполняются для каждого допустимого упорядочения подвыражений полного выражение; в противном случае поведение undefined. [Пример:
i = v[i ++]; / / the behavior is undefined
i = 7 , i++ , i ++; / / i becomes 9
i = ++ i + 1; / / the behavior is undefined
i = i + 1; / / the value of i is incremented
-end пример]
Мы увеличиваем ptr
, а затем назначаем его, что является двумя модификациями, и в этом случае точка последовательности возникает в конце выражения после ;
.
Для C + 11 мы должны перейти в отчет о дефекте 637: правила и примеры секвенирования не согласны, который был отчетом о дефекте, который привел к:
i = ++i + 1;
становится четко определенным поведением в С++ 11, тогда как до С++ 11 это было undefined поведение. Объяснение в этом отчете является одним из лучших, которые я даже видел, и его много раз читал, и он помог мне понять многие концепции в новом свете.
Логика, которая приводит к тому, что это выражение становится четко определенным, выглядит следующим образом:
-
После вычисления значений как его LHS, так и RHS (5.17 [expr.ass], пункт 1) необходимо выполнить следующий побочный эффект.
-
LHS (i) является lvalue, поэтому его вычисление значения включает вычисление адреса i.
-
Чтобы вычислить значение RHS (++ я + 1), необходимо сначала вычислить выражение lvalue ++ i, а затем выполнить преобразование lvalue-to-rval в результате. Это гарантирует, что побочный эффект приращения секвенирован перед вычислением операции сложения, которая, в свою очередь, секвенируется перед побочным эффектом присваивания. Другими словами, он дает четко определенный порядок и конечное значение для этого выражения.
Логика несколько похожа на:
++ptr = ptr1;
-
Вычисления значений LHS и RHS секвенируются перед побочным эффектом присваивания.
-
RHS является lvalue, поэтому его вычисление значения включает в себя вычисление адреса ptr1.
-
Чтобы вычислить значение LHS (++ ptr), необходимо сначала вычислить выражение lvalue ++ ptr, а затем выполнить преобразование lvalue-to-rval в результат. Это гарантирует, что побочный эффект приращения секвенирован перед побочным эффектом присваивания. Другими словами, он дает четко определенный порядок и конечное значение для этого выражения.
Примечание
ОП сказал:
Да, я понимаю, что это беспорядочно, и вы имеете дело с нераспределенным памяти, но он работает и компилируется.
Указатели на объекты без массива считаются массивами размера один для аддитивных операторов, я собираюсь привести проект стандарта С++, но C11 имеет почти такой же текст. Из раздела 5.7
Аддитивные операторы:
Для целей этих операторов указатель на объект nonarray ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта в качестве его типа элемента.
и далее указывается, что один конец конца массива действителен до тех пор, пока вы не разыщите указатель:
[...] Если оба операнда указателя и результат указывают на элементы тот же объект массива, или один за последним элементом массива объект, оценка не должна приводить к переполнению; в противном случае поведение undefined.
так:
++ptr ;
по-прежнему является допустимым указателем.