I was doing tests with expressions and I have found the following example that I don't understand why it gives the following result:
#include <stdio.h>
int main(void)
{
int n = 0, i;
i = n++ + n++;
printf("n = %d; i = %d\n", n, i);
return 0;
}
The result displayed by this program is:
n = 2; i = 0
Shouldn't the result i
be 1
?
Because the increment operator has higher precedence, it is performed before the addition. The first n++
returns 0 (because it is postfix increment), but n
becomes 1
after incrementing. It is then incremented n
again, giving the result of this second operator 1
(back to postfix), and incrementing n
to 2
. Why don't you do this?
This:
causes undetermined behavior because the standard does not specify at what time each post-increment should be evaluated. This causes several possible results to occur:
option 1:
option 2:
It is not usually recommended, therefore, to mix increments on the same variable within the same instruction.
Now the long answer:
C99
The concept of Sequence Points is defined with a paragraph such that:
Which means that a series of points are defined in which the previous expressions must be evaluated. After that point, the values of the expressions become fixed. This implies that between one point and another the expressions can be evaluated in the order that the compiler sees fit.
Ok, and where are those sequence points?
Namely:
As a general rule, at the end of a complete expression:
This section also includes the statements
if
,switch
,while
,do-while
,for
andreturn
.The evaluation of the first expression in the following cases:
Logical AND operator:
Logical OR operator
Comma operator (not to be confused with its use as a separator):
Ternary operator
In a function call, after evaluating all arguments.
Between each statement within an initialization sequence:
C11
This version follows a continuous line with respect to its predecessor C99. Of course, it introduces a new sequence point and modifies the behavior of the ternary operator:
Enter the evaluation of the first operand of the conditional
?
and also between the evaluation of the second and third operands:Immediately before and immediately after each call to a compare function as well as between a call to a compare function and any movement of objects passed as arguments
Versions prior to C++11
Now let's jump into the C++ world. In this case, for versions prior to C++11, the criteria to apply are the same as for C99.
C++11 onwards
The fact is that the theory in this case is complicated because new concepts typical of C++11 are added, so for now I will omit this part. In addition, the behavior is, in general, quite similar.
The most remarkable thing is that with the arrival of C++11, the sequence points as such disappear and we begin to talk about moments of sequencing and the relationships that govern each situation.
So we have concepts like:
In general, it is best not to complicate your life by tempting fate. Write code such that:
It is something that should be punished with a fine and jail. It seems perfect to me that it works on a specific system and that the programmer on duty finds it very cool , but the sad reality is that not only is there no need to compact the code so much, but it is also counterproductive both in terms of readability and portability and maintenance.
Before starting to explain the cause, we are going to describe some concepts in C:
;
) or a complete expression (an expression that is not part of anotherlarger expression).
That is, if the expression with side effect is inside a statement or an entire expression, C ensures that those effects will be applied before proceeding to the next step, but it does not define when they are applied during the evaluation of the statement before advance to the next.
In the above example, the statement
i = n++ + n++;
ensures that the side effects of the expressionn++
will be applied before advancing to the next statement, but it does not define at what point in the execution of the assignment expression they are applied. This is becausen++
it is not a complete expression (it is part of a larger expression) and therefore it is not a sequence point. The sequence point here is the entire statement ending in;
.To obtain the result of the example exposed in the question, the increment of the variable
n
is done after the assignment expression is finished. In this way, the first is evaluatedn++
(without applying the side effect), the second is evaluatedn++
(without modifyingn
either), the assignment is evaluated and then, before moving on to the next statement, the increments are executed by modifying the variablen
.As an alternative, C could also perfectly have performed the modifying increment
n
just after evaluating said operand++
, since the modifying side effect ofn
would perfectly satisfy the constraint of having to be performed before proceeding to the next statement:It should be remembered and highlighted that the order of execution of the two increments is also not defined for cases like this. That is, the second increment can be evaluated first, and then the first. This is because when evaluating operators with the same precedence, if they do not share operands, they can be performed in the order that the implementation decides, not being defined by C. If operators with the same precedence do share operands, then they are evaluated. in operator-dependent association order (multiplication from left to right, assignment from right to left, etc.)
Summary: Therefore, whenever there is an expression that is not a sequence point itself and that produces side effects (
=
,++
,--
, etc), the C standard only ensures that those side effects occur before the completion of the sequence point. sequence to which the expression belongs. It is not guaranteed that they occur at the exact moment of evaluating the expression that produces these side effects.PS: As @eferion explains, you should avoid putting multiple expressions with side effects that affect the result of the expression they belong to.