简体   繁体   中英

Operator associativity, precedence

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.

It's similar to (only with an additional sequence point for illustration):

i = k; // i = 0
k = k + 1;  // k = 1

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.

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

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.

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.

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM