2014-09-03 2 views
69

Я обманывал какой-то код и видел что-то, что я не понимаю «почему».Разница между 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» в левой части оператора присваивания. Справедливо.

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

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

+12

Насколько я знаю, '++ i' не возвращает l-значение в C. Независимо, это UB, когда вы изменяете переменную 2 раза между двумя последовательными точками последовательности. Другими словами, неуказано, увеличивается ли значение сначала или оно назначается первым. – bolov

+1

@OllieFord Я никогда не понимал, что UB означает, что он не может компилироваться. Я имею в виду, люди говорят, что все может случиться, но я считаю, что все может случиться * при запуске кода *. – juanchopanza

+3

@juanchopanza код руны, это UB, поэтому программа возвращается во времени и останавливает процесс компиляции. Итак ... да ... – bolov

ответ

73

В 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):

An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment, but is not an lvalue.

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

An assignment operator shall have a modifiable lvalue as its left operand.

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

The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.

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

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

В C++ 11 поведение ++ptr = ptr1 четко определено. Это яснее, если записать его в виде

(ptr += 1) = ptr1; 

Поскольку C++ 11, стандарт C++ предусматривает, что (§5.17 [выраж.осла]/р1)

In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression. With respect to an indeterminately-sequenced function call, the operation of a compound assignment is a single evaluation.

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

+1

В вашей последней цитате «присвоение» должно означать «побочный эффект задания»? –

+1

@MattMcNabb Действительно. –

+0

На самом деле я не понимаю, почему в C значение выражения (не составного) присваивания называется именем его левого операнда; это фактически значение его ** правого ** операнда (а затем, очевидно, это rvalue). Конечно, в таких случаях, как 'i = i + 1', это не значение, которое получило бы правильное выражение операнда, если бы оно было снова оценено, но аналогичный оператор не является истинным для выражения левого операнда либо в несколько надуманных случаях, как' int а [2] = {0,3}; а [а [0]] = 1 '. –

17

В C результат до и после приращения является rvalues ​​и мы не можем назначить к RValue, нужно именующее выражение (also see: Understanding lvalues and rvalues in C and C++). Мы можем увидеть, перейдя в раздел draft C11 standard6.5.2.4Postfix инкремента и декремента операторы который говорит (курсив мой идти вперед):

The result of the postfix ++ operator is the value of the operand. [...] See the discussions of additive operators and compound assignment for information on constraints, types, and conversions and the effects of operations on pointers. [...]

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

[...] An assignment expression has the value of the left operand after the assignment, but is not an lvalue.[...]

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

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

[...]See the discussions of additive operators and compound assignment for information on constraints, types, side effects, and conversions and the effects of operations on pointers.

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

В C++ пост-инкремент также Rvalue, более конкретно prvalue мы можем подтвердить это, перейдя в раздел 5.2.6инкремента и декремента, который говорит:

[...]The result is a prvalue. The type of the result is the cv-unqualified version of the type of the operand[...]

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

Для C++ это рассматривается в разделе 5.3.2инкремента и декремента, который говорит:

[...]The result is the updated operand; it is an lvalue, and it is a bit-field if the operand is a bit-field.[...]

Чтобы понять, является ли:

++ptr = ptr1; 

хорошо определен или не в C++ нам нужны два различных подхода один для pre C++ 11 и один для C++ 11.

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

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.57) Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. [ Example:

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 example ]

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

Для C + 11, мы должны идти к defect report 637: Sequencing rules and example disagree который был отчет дефект, в результате:

i = ++i + 1; 

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

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

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

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

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

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

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

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

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

Примечание

ОП сказал:

Yes, I understand it's messy and you're dealing with unallocated memory, but it works and compiles.

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

For the purposes of these operators, a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

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

[...]If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

так:

++ptr ; 

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

Смежные вопросы