it-roy-ru.com

Разница между C и 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;

Постфикс запускает ошибку компиляции, и я ее получаю. У вас есть константа "ptr + 1" слева от оператора присваивания. Справедливо.

Префикс 1 компилируется и РАБОТАЕТ в C++. Да, я понимаю, что это грязно, и вы имеете дело с нераспределенной памятью, но это работает и компилируется. В Си это не компилируется, возвращая ту же ошибку, что и постфикс "lvalue требуется как левый операнд присваивания". Это происходит независимо от того, как оно написано, с двумя операторами "=" или с синтаксисом "++ ptr".

В чем разница между тем, как C обрабатывает такое назначение, и тем, как C++ обрабатывает его?

70
Moe45673

И в C, и в C++ результат x++ является rvalue, поэтому вы не можете его присвоить.

В C ++x эквивалентен x += 1 (стандарт C §6.5.3.1/p2; все ссылки на C стандартные для WG14 N1570). В C++ ++x эквивалентен x += 1, если x не является bool (стандарт C++ §5.3.2 [expr.pre.incr]/p1; все стандартные ссылки C++ относятся к WG21 N3936).

В C результатом выражения присваивания является rvalue (стандарт C §6.5.16/p3):

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

Поскольку это не lvalue, вы не можете присвоить ему: (стандарт C §6.5.16/p2 - обратите внимание, что это ограничение)

Оператор присваивания должен иметь изменяемое lvalue в качестве своего левого операнда.

В C++ результатом выражения присваивания является lvalue (стандарт C++ §5.17 [expr.ass]/p1):

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

Таким образом, ++ptr = ptr1; является диагностируемым нарушением ограничения в C, но не нарушает ни одно диагностируемое правило в C++.

Однако, до C++ 11, ++ptr = ptr1; имеет неопределенное поведение, так как он дважды модифицирует ptr между двумя соседними точками последовательности.

В C++ 11 поведение ++ptr = ptr1 становится хорошо определенным. Будет понятнее, если мы переписаем

(ptr += 1) = ptr1;

Начиная с C++ 11, стандарт C++ обеспечивает это (§5.17 [expr.ass]/p1)

Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и до вычисления значения выражения присваивания. Что касается вызова функции с неопределенной последовательностью, операция составного присваивания является единственной оценкой.

Таким образом, присвоение, выполняемое =, упорядочивается после вычисления значения ptr += 1 и ptr1. Назначение, выполняемое +=, упорядочивается до вычисления значения ptr += 1, и все вычисления значения, требуемые +=, обязательно упорядочиваются перед этим назначением. Таким образом, последовательность здесь четко определена и нет неопределенного поведения.

73
T.C.

В C результатом приращения до и после являются значения rval, и мы не можем присвоить rvalue, нам нужно lvalue (также см .: Понимание lvalues ​​и rvalues ​​в C и C++). Мы можем увидеть, перейдя к разделу черновик стандарта C116.5.2.4Операторы приращения и уменьшения Postfix, который говорит (выделение будет идти вперед):

результат оператора postfix ++ является значением операнда , [...] См. Обсуждение аддитивных операторов и составного присваивания для получения информации об ограничениях, типах и преобразованиях и влиянии операций на указатели. [...]

Таким образом, результатом постинкремента является значение, что является синонимом для rvalue, и мы можем подтвердить это, перейдя в раздел 6.5.16Операторы присваивания, который параграф выше указывает нам на дальнейшее понимание ограничений и результатов, он говорит:

[...] Выражение присваивания имеет значение левого операнда после присваивания , но не является lvalue . [...]

что еще раз подтверждает, что результат постинкремента не является lvalue.

Для предварительного увеличения мы можем видеть из раздела 6.5.3.1Префиксные операторы увеличения и уменьшения, который говорит:

[...] См. Обсуждение аддитивных операторов и составного присваивания для получения информации об ограничениях, типах, побочных эффектах и ​​преобразованиях и влиянии операций на указатели.

также указывает на 6.5.16, как это делает постинкремент, и поэтому результат предварительного инкремента в C также не является lvalue.

В C++ post-increment также --- rvalue, точнее --- prvalue мы можем подтвердить это, перейдя в раздел 5.2.6Increment and decment, который говорит:

[...] Результатом является предварительное значение. Тип результата - cv-неквалифицированная версия типа операнда [... ]

Относительно предварительного приращения C и C++ различаются. В C результат --- rvalue, а в C++ - lvalue, который объясняет, почему ++ptr = ptr1; работает в C++, но не в C.

Для C++ это описано в разделе 5.3.2Увеличение и уменьшение, в котором говорится:

[...] Результатом является обновленный операнд; это lvalue , и это битовое поле, если операнд является битовым полем. [...]

Чтобы понять:

++ptr = ptr1;

хорошо определено или нет в C++, нам нужны два разных подхода: один для предварительного C++ 11 и один для C++ 11.

До C++ 11 это выражение вызывает неопределенное поведение , поскольку оно модифицирует объект более одного раза в одной и той же точке последовательности. Мы можем убедиться в этом, перейдя к предварительному стандартному разделу C++ 11 5Выражения, в котором говорится:

Если не указано иное, порядок вычисления операндов отдельных операторов и подвыражений отдельных выражений и порядок возникновения побочных эффектов не определены.57) Между предыдущей и следующей точкой последовательности скалярный объект должен иметь свое сохраненное значение, измененное не более одного раза путем оценки выражения. Кроме того, к предыдущему значению следует обращаться только для определения значения, которое будет сохранено. Требования этого параграфа должны выполняться для каждого допустимого порядка подвыражений полного выражения; в противном случае поведение не определено. [ Пример:

 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

- конец примера]

Мы увеличиваем ptr и затем присваиваем ему, что составляет две модификации, и в этом случае точка последовательности появляется в конце выражения после ;.

Для C + 11 мы должны перейти к отчет о дефектах 637: правила последовательности и пример не согласен , который был отчет о дефектах, который привел к:

i = ++i + 1;

становится хорошо определенным поведением в C++ 11, тогда как до C++ 11 это было неопределенное поведение . Объяснение в этом отчете - одно из лучших, которые я когда-либо видел, и много раз его читали, что помогло мне понять многие концепции в новом свете.

Логика, которая приводит к тому, что это выражение становится четко определенным поведением, выглядит следующим образом:

  1. Побочный эффект присвоения должен быть упорядочен после вычислений значений как его LHS, так и RHS (5.17 [expr.ass] параграф 1).

  2. LHS (i) является lvalue, поэтому вычисление его значения включает в себя вычисление адреса i.

  3. Для вычисления значения RHS (++ i + 1) необходимо сначала вычислить значение lvalue-выражение ++ i, а затем выполнить преобразование lvalue-to-rvalue для результата. Это гарантирует, что побочный эффект приращения упорядочен перед вычислением операции сложения, которая, в свою очередь, упорядочена перед побочным эффектом назначения. Другими словами, он дает четко определенный порядок и окончательное значение для этого выражения.

Логика несколько похожа на:

++ptr = ptr1;
  1. Вычисления значений LHS и RHS секвенируются до побочного эффекта назначения.

  2. RHS является lvalue, поэтому вычисление его значения включает в себя вычисление адреса ptr1.

  3. Чтобы вычислить значение LHS (++ ptr), необходимо сначала вычислить значение lvalue expression ++ ptr, а затем выполнить преобразование lvalue в rvalue для результата. Это гарантирует, что побочный эффект приращения упорядочен до побочного эффекта назначения. Другими словами, он дает четко определенный порядок и окончательное значение для этого выражения.

Заметка

ОП сказал:

Да, я понимаю, что это грязно, и вы имеете дело с нераспределенной памятью, но это работает и компилируется.

Указатели на объекты, не являющиеся массивами, считаются массивами первого размера для аддитивных операторов, я собираюсь процитировать черновик стандарта C++, но текст C11 имеет почти такой же текст. Из раздела 5.7Аддитивные операторы:

Для целей этих операторов указатель на объект без массива ведет себя так же, как указатель на первый элемент массива длиной один с типом объекта в качестве его типа элемента.

и далее говорит нам, что указание на один конец конца массива допустимо, если вы не разыменовали указатель:

[...] Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива, или один после последнего элемента объекта массива оценка не должна приводить к переполнению; в противном случае поведение не определено.

так:

++ptr ;

все еще действительный указатель.

17
Shafik Yaghmour