I have been fooling around with some code and saw something that I don't understand the "why" of.
int i = 6;
int j;
int *ptr = &i;
int *ptr1 = &j
j = i++;
//now j == 6 and i == 7. Straightforward.
What if you put the operator on the left side of the equals sign?
++ptr = ptr1;
is equivalent to
(ptr = ptr + 1) = ptr1;
whereas
ptr++ = ptr1;
is equivalent to
ptr = ptr + 1 = ptr1;
The postfix runs a compilation error and I get it. You've got a constant "ptr + 1" on the left side of an assignment operator. Fair enough.
The prefix one compiles and WORKS in C++. Yes, I understand it's messy and you're dealing with unallocated memory, but it works and compiles. In C this does not compile, returning the same error as the postfix "lvalue required as left operand of assignment". This happens no matter how it's written, expanded out with two "=" operators or with the "++ptr" syntax.
What is the difference between how C handles such an assignment and how C++ handles it?
In both C and C++, the result of
x++
is an rvalue, so you can't assign to it.In C,
++x
is equivalent tox += 1
(C standard §6.5.3.1/p2; all C standard cites are to WG14 N1570). In C++,++x
is equivalent tox += 1
ifx
is not abool
(C++ standard §5.3.2 [expr.pre.incr]/p1; all C++ standard cites are to WG21 N3936).In C, the result of an assignment expression is an rvalue (C standard §6.5.16/p3):
Because it's not an lvalue, you can't assign to it: (C standard §6.5.16/p2 - note that this is a constraint)
In C++, the result of an assignment expression is an lvalue (C++ standard §5.17 [expr.ass]/p1):
So
++ptr = ptr1;
is a diagnosable constraint violation in C, but does not violate any diagnosable rule in C++.However, pre-C++11,
++ptr = ptr1;
has undefined behavior, as it modifiesptr
twice between two adjacent sequence points.In C++11, the behavior of
++ptr = ptr1
becomes well defined. It's clearer if we rewrite it asSince C++11, the C++ standard provides that (§5.17 [expr.ass]/p1)
So the assignment performed by the
=
is sequenced after the value computation ofptr += 1
andptr1
. The assignment performed by the+=
is sequenced before the value computation ofptr += 1
, and all value computations required by the+=
are necessarily sequenced before that assignment. Thus, the sequencing here is well-defined and there is no undefined behavior.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):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: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: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: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:To understand whether:
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: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:
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:
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:
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:and further tells us pointing one past the end of an array is valid as long as you don't dereference the pointer:
so:
is still a valid pointer.