In C the result of pre and post increment are rvalues and we can not assign to an rvalue, we need an lvalue(also see: Understanding lvalues and rvalues in C and C++) . We can see by going to the draft C11 standard section 6.5.2.4
Postfix increment and decrement operators which says (emphasis mine going forward):
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. [...]
So the result of post-increment is a value which is synonymous for rvalue and we can confirm this by going to section 6.5.16
Assignment operators which the paragraph above points us to for further understanding of constraints and results, it says:
[...] An assignment expression has the value of the left operand after the
assignment, but is not an lvalue.[...]
which further confirms the result of post-increment is not an lvalue.
For pre-increment we can see from section 6.5.3.1
Prefix increment and decrement operators which says:
[...]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.
also points back to 6.5.16
like post-increment does and therefore the result of pre-increment in C is also not an lvalue.
In C++ post-increment is also an rvalue, more specifically a prvalue we can confirm this by going to section 5.2.6
Increment and decrement which says:
[...]The result is a prvalue. The type of the result is the cv-unqualified
version of the type of the operand[...]
With respect to pre-increment C and C++ differ. In C the result is an rvalue while in C++ the result is a lvalue which explains why ++ptr = ptr1;
works in C++ but not C.
For C++ this is covered in section 5.3.2
Increment and decrement which says:
[...]The result is the updated operand; it is an lvalue, and it is a
bit-field if the operand is a bit-field.[...]
To understand whether:
++ptr = ptr1;
is well defined or not in C++ we need two different approaches one for pre C++11 and one for C++11.
Pre C++11 this expression invokes undefined behavior, since it is modifying the object more than once within the same sequence point. We can see this by going to a Pre C++11 draft standard section 5
Expressions which says:
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 ]
We are incrementing ptr
and then subsequently assigning to it, which is two modifications and in this case the sequence point occurs at the end of the expression after the ;
.
For C+11, we should go to defect report 637: Sequencing rules and example disagree which was the defect report that resulted in:
i = ++i + 1;
becoming well defined behavior in C++11 whereas prior to C++11 this was undefined behavior. The explanation in this report is one of best I have even seen and reading it many times was enlightening and helped me understand many concepts in a new light.
The logic that lead to this expression becoming well defined behavior goes as follows:
The assignment side-effect is required to be sequenced after the value computations of both its LHS and RHS (5.17 [expr.ass] paragraph 1).
The LHS (i) is an lvalue, so its value computation involves computing the address of i.
In order to value-compute the RHS (++i + 1), it is necessary to first value-compute the lvalue expression ++i and then do an lvalue-to-rvalue conversion on the result. This guarantees that the incrementation side-effect is sequenced before the computation of the addition operation, which in turn is sequenced before the assignment side effect. In other words, it yields a well-defined order and final value for this expression.
The logic is somewhat similar for:
++ptr = ptr1;
The value computations of the LHS and RHS are sequenced before the assignment side-effect.
The RHS is an lvalue, so its value computation involves computing the address of ptr1.
In order to value-compute the LHS (++ptr), it is necessary to first value-compute the lvalue expression ++ptr and then do an lvalue-to-rvalue conversion on the result. This guarantees that the incrementation side-effect is sequenced before the assignment side effect. In other words, it yields a well-defined order and final value for this expression.
Note
The OP said:
Yes, I understand it's messy and you're dealing with unallocated
memory, but it works and compiles.
Pointers to non-array objects are considered arrays of size one for additive operators, I am going to quote the draft C++ standard but C11 has almost the exact same text. From section 5.7
Additive operators:
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.
and further tells us pointing one past the end of an array is valid as long as you don't dereference the pointer:
[...]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.
so:
++ptr ;
is still a valid pointer.
++i
doesn’t return an l-value in C. Regardless, this is UB as you modify the variable 2 times between two consecutive sequence points. In other words it’s unspecified whether the value is incremented first or it is assigned first. – MunafoError: UB
? – Jessabell++ptr
is not an l-value, why would you expect(ptr = ptr + 1)
to be? – Roguery++x
is nothing more thanx += 1
). – Bermejo++ptr = ptr1
is not UB in C++ (>= 11). There is a sequenced-before relationship between the side-effect of the prefix++
and the side-effect of=
. – Epp=
works in C++11 in respect to sequencing . – Munafo=
, it's still rather simple: the left and right hand side are computed, THEN the side-effect happens, THEN the value of the assignment-expression is computed. The (value) computation of the lhs and rhs are unsequenced. The value computation of the lhs in this case requires the side-effect of++
to have happended. – Eppclang
runs its undefined behavior sanitizer at runtime so it can catch more cases. – Casillas