Related
I just wonder if, for the following code, the compiler uses associativity/precedence alone or some other logic to evaluate.
int i = 0, k = 0;
i = k++;
If we evaluate based on associativity and precedence, postfix ++ has higher precedence than =, so k++(which becomes 1) is evaluated first and then comes =, now the value of k which is 1 is assigned to i.
So the value of i and k would be 1. However, the value of i is 0 and k is 1.
So I think that the compiler splits this i = k++; into two (i = k; k++;). So here compiler is not going for the statements associativity/precedence, it splits the line as well. Can someone explain how the compiler resolves these kinds of statements?
++ does two separate things.
k++ does two things:
It has the value of k before any increment is performed.
It increments k.
These are separate:
Producing the value of k occurs as part of the main evaluation of i = k++;.
Incrementing k is a side effect. It is not part of the main evaluation. The program may increment the value of k after evaluating the rest of the expression or during it. It may even increment the value before the rest of the expression, as long as it “remembers” the pre-increment value to use for the expression.
Precedence and associativity are not involved.
This effectively has nothing to do with precedence or associativity. The increment part of a ++ operator is always separate from the main evaluation of an expression. The value used for k++ is always the value of k before the increment regardless of what other operators are present.
Supplement
It is important to understand that the increment part of ++ is detached from the main evaluation and is sort of “floating around” in time–it is not anchored to a certain spot in the code, and you do not control when it occurs. This is important because if there is another use or modification of the operand, such as in k * k++, the increment can occur before, during, or after the main evaluation of the other occurrence. When this happens, the C standard does not define the behavior of the program.
Postfix operators have higher precedence than assignment operators.
This expression with the assignment operator
i = k++
contains two operands.
It is equivalently can be rewritten like
i = ( k++ );
The value of the expression k++ is 0. So the variable i will get the value 0.
The operands of the assignment operator can be evaluated in any order.
According to the C Standard (6.5.2.4 Postfix increment and decrement operators)
2 The result of the postfix ++ operator is the value of the operand.
As a side effect, the value of the operand object is incremented (that
is, the value 1 of the appropriate type is added to it).
And (6.5.16 Assignment operators)
3 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,111) but is not an lvalue. The type of an
assignment expression is the type the left operand would have after
lvalue conversion. The side effect of updating the stored value of
the left operand is sequenced after the value computations of the left
and right operands. The evaluations of the operands are unsequenced.
Unlike C++, C does not have "pass by reference". Only "pass by value". I'm going to borrow some C++ to explain. Let's implement the functionality of ++ for both postfix and prefix as regular functions:
// Same as ++x
int inc_prefix(int &x) { // & is for pass by reference
x += 1;
return x;
}
// Same as x++
int inc_postfix(int &x) {
int tmp = x;
x += 1;
return tmp;
}
So your code is now equivalent to:
i = inc_postfix(k);
EDIT:
It's not completely equivalent for more complex things. Function calls introduces sequence points for instance. But the above is enough to explain what happens for OP.
It's similar to (only with an additional sequence point for illustration):
i = k; // i = 0
k = k + 1; // k = 1
Operator associativity doesn't apply here. Operator precedence merely states which operand that sticks to which operator. It's not particularly relevant in this case, it just says that the expression should be parsed as i = (k++); and not as (i = k)++; which wouldn't make any sense.
From there on, how this expression is evaluated/executed is specified by specific rules for each operator. The postfix operator is specified to behave as (6.5.2.4):
The value computation of the result is sequenced before the side effect of
updating the stored value of the operand.
That is, k++ is guaranteed to evaluate to 0 and then at some point later on, k is increased by 1. We don't really know when, only that it happens somewhere between the point when k++ is evaluated but before the next sequence point, in this case the ; at the end of the line.
The assignment operator behaves as (6.5.16):
The side effect of updating the stored value of the left operand is
sequenced after the value computations of the left and right operands.
In this case, the right operand of = has its value computed before updating the left operand.
In practice, this means that the executable can look as either this:
k is evaluated to 0
set i to 0
increase k by 1
semicolon/sequence point
Or this:
k is evaluated to 0
increase k by 1
set i to 0
semicolon/sequence point
Precedence and associativity only affect how operators and operands are associated with each other - they do not affect the order in which expressions are evaluated. Precedence rules dictate that
i = k++
is parsed as
i = (k++)
instead of something like
(i = k)++
The postfix ++ operator has a result and a side effect. In the expression
i = k++
the result of k++ is the current value of k, which gets assigned to i. The side effect is to increment k.
It's logically equivalent to writing
tmp = k
i = tmp
k = k + 1
with the caveat that the assignment to i and the update to k can happen in any order - the operations can even be interleaved with each other. What matters is that i gets the value of k before the increment and that k gets incremented, not necessarily the order in which those operations occur.
The fundamental issue here is that precedence is not the right way to think about what
i = k=+;
means.
Let's talk about what k++ actually means. The definition of k++ is that if gives you the old value of k, and then adds 1 to the stored value of k. (Or, stated another way, it takes the old value of k, plus 1, and stores it back into k, while giving you the old value of k.)
As far as the rest of the expression is concerned, the important thing is what the value of k++ is. So when you say
i = k++;
the answer to the question of "What gets stored in i?" is, "The old value of k".
When we answer the question of "What gets stored in i?", we don't think about precedence at all. We think about the meaning of the postfix ++ operator.
See also this older question.
Postscript: The other thing you have to be really careful about is when you think about the side question, "When does it store the new value into k? It turns out that's a really hard question to answer, because the answer is not as well defined as you might like. The new value gets stored back into k sometime before the end of the larger expression it's in (formally, "before the next sequence point"), but we don't know whether it happens before or after, say, the point at which the thing gets stored into i, or before or after other interesting points in the expression.
Ahh, this is quite an interesting question. To help you understand better, this is what actually happens.
I'm going to try to explain using a bit of operator overloading concepts from C++, so bear with me if you do not know C++.
This is how you would overload the postfix-increment operator:
int operator++(int) // Note that the 'int' parameter is just a C++ way of saying that this is the postfix and not prefix operator
{
int copy = *this; // *this just means the current object which is calling the function
*this += 1;
return copy;
}
Essentially what the postfix-increment operator does is that it creates a copy of the operand, increases the original variable, and then returns the copy.
In your case of i = k++, k++ does actually happen first but the value returned is actually k (think of it like a function call). This then gets assigned to i.
According to sequence point definition, sequence points are "specified points in the execution sequence called sequence points, all side effects of previous evaluations are guaranteed to be complete"
So in the below program, all side effects of ++ operator must have been performed before going to second part of && operator, i.e, i should be incremented to 1 as && is a sequence point.
#include<stdio.h>
int main()
{
int i=0,a;
a=i++&&1;
printf("%d",a);
getchar();
return 0;
}
Expected output:
1 (1&&1=1)
actual output :
0
Why doesn't i increment before 2nd part?
Using the ternary operator also gives same output:
#include<stdio.h>
int main()
{
int i=0,a;
a=(i++)?1:0;
printf("%d",a);
getchar();
return 0;
}
The ternary operator is also a sequence point. So shouldn't this give output 1 instead of 0?
i++
Evaluates to previous value of i.
As a side effect value of i is incremented by 1.
So yes the sequence point is there but the expression i++ evaluates to 0 (though value of i is 1 at the same time)
For the expected results use ++i instead of i++.
From 6.5.2.4 Postfix increment and decrement operators in C11 specs:
The result of the postfix ++ operator is the value of the operand. As
a side effect, the value of the operand object is incremented (that
is, the value 1 of the appropriate type is added to it). See the
discussions of additive operators and compound assignment for
information on constraints, types, and conversions and the effects of
operations on pointers. The value computation of the result is
sequenced before the side effect of updating the stored value of the
operand. With respect to an indeterminately-sequenced function call,
the operation of postfix ++ is a single evaluation. Postfix ++ on an
object with atomic type is a read-modify-write operation with
memory_order_seq_cst memory order semantics.98)
98) Where a pointer to an atomic object can be formed and E has
integer type, E++ is equivalent to the following code sequence where T
is the type of E:
T *addr = &E;
T old = *addr;
T new;
do {
new = old + 1;
} while (!atomic_compare_exchange_strong(addr, &old, new));
with old being the result of the operation. Special care must be taken
if E has floating type; see 6.5.16.2.)
After reading this answer about undefined behavior and sequence points, I wrote a small program:
#include <stdio.h>
int main(void) {
int i = 5;
i = (i, ++i, 1) + 1;
printf("%d\n", i);
return 0;
}
The output is 2. Oh God, I didn't see the decrement coming! What is happening here?
Also, while compiling the above code, I got a warning saying:
px.c:5:8: warning: left-hand operand of comma expression has no effect
[-Wunused-value] i = (i, ++i, 1) + 1;
^
Why? But probably it will be automatically answered by the answer of my first question.
In the expression (i, ++i, 1), the comma used is the comma operator
the comma operator (represented by the token ,) is a binary operator that evaluates its first operand and discards the result, and then evaluates the second operand and returns this value (and type).
Because it discards its first operand, it is generally only useful where the first operand has desirable side effects. If the side effect to the first operand does not takes place, then the compiler may generate warning about the expression with no effect.
So, in the above expression, the leftmost i will be evaluated and its value will be discarded. Then ++i will be evaluated and will increment i by 1 and again the value of the expression ++i will be discarded, but the side effect to i is permanent. Then 1 will be evaluated and the value of the expression will be 1.
It is equivalent to
i; // Evaluate i and discard its value. This has no effect.
++i; // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;
Note that the above expression is perfectly valid and does not invoke undefined behavior because there is a sequence point between the evaluation of the left and right operands of the comma operator.
Quoting from C11, chapter 6.5.17, Comma operator
The left operand of a comma operator is evaluated as a void expression; there is a
sequence point between its evaluation and that of the right operand. Then the right
operand is evaluated; the result has its type and value.
So, in your case,
(i, ++i, 1)
is evaluated as
i, gets evaluated as a void expression, value discarded
++i, gets evaluated as a void expression, value discarded
finally, 1, value returned.
So, the final statement looks like
i = 1 + 1;
and i gets to 2. I guess this answers both of your questions,
How i gets a value 2?
Why there is a warning message?
Note: FWIW, as there is a sequence point present after the evaluation of the left hand operand, an expression like (i, ++i, 1) won't invoke UB, as one may generally think by mistake.
i = (i, ++i, 1) + 1;
Let's analyse it step by step.
(i, // is evaluated but ignored, there are other expressions after comma
++i, // i is updated but the resulting value is ignored too
1) // this value is finally used
+ 1 // 1 is added to the previous value 1
So we obtain 2. And the final assignment now:
i = 2;
Whatever was in i before it's overwritten now.
The outcome of
(i, ++i, 1)
is
1
For
(i,++i,1)
the evaluation happens such that the , operator discards the evaluated value and will retain just the right most value which is 1
So
i = 1 + 1 = 2
You'll find some good reading on the wiki page for the Comma operator.
Basically, it
... evaluates its first operand and discards the result, and then evaluates the second operand and returns this value (and type).
This means that
(i, i++, 1)
will, in turn, evaluate i, discard the result, evaluate i++, discard the result, and then evaluate and return 1.
You need to know what the comma operator is doing here:
Your expression:
(i, ++i, 1)
The first expression, i, is evaluated, the second expression, ++i, is evaluated, and the third expression, 1, is returned for the whole expression.
So the result is: i = 1 + 1.
For your bonus question, as you see, the first expression i has no effect at all, so the compiler complains.
Comma has an 'inverse' precedence. This is what you will get from old books and C manuals from IBM (70s/80s). So the last 'command' is what is used in parent expression.
In modern C its use is strange but is very interesting in old C (ANSI):
do {
/* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);
While all operations (functions) are called from left to right, only the last expression will be used as a result to conditional 'while'.
This prevent handling of 'goto's to keep a unique block of commands to run before condition check.
EDIT: This avoid also a call to a handling function which could take care of all logic at left operands and so return the logical result. Remember that, we had not inline function in the past of C. So, this could avoid a call overhead.
I was reading C Traps and Pitfalls and read that the following code may work on some implementations and won't on others due to an undefined order of = and ++. Is this still true of C?
int i = 0;
while (i < n)
y[i] = x[i++];
If so, that's really incredible.
Nothing incredible. Pretty defined undefined behavior. Read more about sequence points.
Just writing as:
int i = 0;
while (i < n)
{
y[i] = x[i];
i++;
}
is safer and more readable.
The postfix ++ has a result and a side effect. The result is the current value of the operand. The side effect is that the operand gets incremented by one. Where the problem comes in is that the side effect doesn't have to be applied immediately after the expression has been evaluated; it only has to be applied before the next sequence point.
From the C language standard (n1256):
6.5 Expressions
...
2 Between the previous and next sequence point an object shall have its stored value
modified at most once by the evaluation of an expression.72) Furthermore, the prior value
shall be read only to determine the value to be stored.73)
...
72) A floating-point status flag is not an object and can be set more than once within an expression.
73) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i = i + 1;
a[i] = i;
It is not particularly suprising that this code has undefined behaviour, because it's semantically ambiguous: In y[i], which value of i is intended? The value before the increment, or after? (Bear in mind that the = operator does not specify that one side is evaluated before the other)
Yes, it's still UB.
§ 1.9p7 At certain specified
points in the execution sequence
called sequence points, all side
effects of previous evaluations shall
be complete and no side effects of
subsequent evaluations shall have
taken place. (§1.9/7)
Well, i++ means "increment i after using its value", so it I think it's correct (well, I changed idea reading the other posts). Rather I would do:
while((i++) < n)
y[i] = x[i];
This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
In what order does evaluation of post-increment operator happen?
Consider the following snippet(in C):
uint8_t index = 10;
uint8_t arr[20];
arr[index++] = index;
When I compile this with gcc, it sets arr[10] to 10, which means that the postfix increment isn't being applied until after the entire assignment expression. I found this somewhat surprising, as I was expecting the increment to return the original value(10) and then increment to 11, thereby setting arr[10] to 11.
I've seen lots of other posts about increment operators in RValues, but not in LValue expressions.
Thanks.
Some standard language:
6.5 Expressions
1 An expression is a sequence of operators and operands that specifies computation of a
value, or that designates an object or a function, or that generates side effects, or that
performs a combination thereof.
2 Between the previous and next sequence point an object shall have its stored value
modified at most once by the evaluation of an expression.72) Furthermore, the prior value shall be read only to determine the value to be stored.73)
3 The grouping of operators and operands is indicated by the syntax.74) Except as specified later (for the function-call (), &&, ||, ?:, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified.
Paragraph 2 explicitly renders expressions of the form a[i++] = i undefined; the prior value of i isn't just being read to determine the result of i++. Thus, any result is allowed.
Beyond that, you cannot rely on the side effect of the ++ operator to be applied immediately after the expression is evaluated. For an expression like
a[i++] = j++ * ++k
the only guarantee is that the result of the expression j++ * ++k is assigned to the result of the expression a[i++]; however, each of the subexpressions a[i++], j++, and ++k may be evaluated in any order, and the side effects (assigning to a[i], updating i, updating j, and updating k) may be applied in any order.
The line
arr[index++] = index;
causes undefined behaviour. Read the C standard for more details.
The essence you should know is: You should never read and change a variable in the same statement.
Assignment operation works from right to left, i.e. right part of expression calculated first and then it is assigned to the left part.