Подтвердить что ты не робот

Разница между 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 обрабатывает такое назначение и как С++ обрабатывает его?

4b9b3361

Ответ 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 ;

по-прежнему является допустимым указателем.