17

According to ISO C11 - 6.5.16.3, it says that

  1. 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, 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.

So I guess this means that, for example,

int x = 10;
x = 5 + 10;
  1. Left operand x is evaluated to 10 and right operand is evaluated to 15.
  2. Right operand value is stored in the object designated by the left operand x.

But if the purpose of the assignment is to store the evalauted value of right operand(just like in step2), why is evaluation of left operand necessary? What's the point of evaluating the left operand?

Jin
  • 1,902
  • 3
  • 15
  • 26

5 Answers5

27

When x is evaluated as an lvalue, it does not evaluate to 10. It evaluates to an lvalue where the value of the RHS can be stored. If the LHS does not evaluate to an lvalue, the statement would be an error.

From the C99 Standard (6.3.2.1/1):

An lvalue is an expression (with an object type other than void) that potentially designates an object; if an lvalue does not designate an object when it is evaluated, the behavior is undefined.

The evaluation of the LHS as an lvalue is trivial when you have a simple variable, such as

 x = 10;

However, it can be more complex.

 double array[10];
 int getIndex();   // Some function that can return an index based
                   // on other data and logic.

 array[getIndex()+1] = 10.0;

 // This looks like a function call that returns a value.
 // But, it still evaluates to a "storage area".
 int *getIndex2() { return(&array[0]); }
 *getIndex2()=123.45; // array[0]=123.45

If getIndex() returns 5, then the LHS evaluates to an lvalue that designates the 7-th element of the array.

blackpen
  • 2,339
  • 13
  • 15
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Can you explain me in detail what it means to be evaluated to an lvalue? – Jin Aug 20 '16 at 07:27
  • Does it mean that `array[getIndex()+1]` is not evaluated to some pointer value but evaluated to some pointer type expression which points to int? – Jin Aug 20 '16 at 07:43
  • Wow I have never heard of such concept, evaluating to an object. Do you know any good references about that topic? – Jin Aug 20 '16 at 07:48
  • Any book on C should explain that when discussing arrays. If you have access to the C99 standard, you can find this in Section 6.5.2.1/2: *A postfix expression followed by an expression in square brackets `[]` is a subscripted designation of an element of an array object.* – R Sahu Aug 20 '16 at 07:55
  • 1
    As another example, you can have `*f() = 10;`, that is a function-call on the LHS that returns a pointer that is then dereferenced yielding an object to assign to. – Paul J. Lucas Aug 20 '16 at 20:17
15

A "left operand" can be much more complicated than your simple x in your example (which admittedly isn't really a challenge to evaluate):

*(((unsigned long*)target)++) = longValue;

Definitely needs a bit of evaluation on the LHS. Your quoted sentence refers to what needs to be done on the left-hand side of the assignment in order to find the proper lvalue to receive the assignment.

tofro
  • 5,640
  • 14
  • 31
4

just to convince myself (if not already done) from a "Judas" point of view, which justifies that my post only answers to the simple question in your simple case.

small proof showing that in your simple example gcc does just what it is needed, not more:

code:

int main()
{
int x = 10;
x = 5 + 10;

return x;
}

build with debug

K:\jff\data\python\stackoverflow\c>gcc -g -std=c11 -c assign.c

objdump with intermixed C/asm code

K:\jff\data\python\stackoverflow\c>objdump -d -S assign.o

assign.o:     file format pe-x86-64


Disassembly of section .text:

0000000000000000 <main>:
int main()
{
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 30             sub    $0x30,%rsp
   8:   e8 00 00 00 00          callq  d <main+0xd>
int x = 10;
   d:   c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
x = 5 + 10;
  14:   c7 45 fc 0f 00 00 00    movl   $0xf,-0x4(%rbp)

return x;
  1b:   8b 45 fc                mov    -0x4(%rbp),%eax
}
  1e:   90                      nop
  1f:   48 83 c4 30             add    $0x30,%rsp
  23:   5d                      pop    %rbp
  24:   c3                      retq
  25:   90                      nop
  26:   90                      nop
  27:   90                      nop
  28:   90                      nop
  29:   90                      nop
  2a:   90                      nop
  2b:   90                      nop
  2c:   90                      nop
  2d:   90                      nop
  2e:   90                      nop
  2f:   90                      nop

As stated in the other (nice) answers, not willing to paraphrase, but if the expression is more complex, the address to store the value to must be computed, so an evaluation of some kind is necessary.

EDIT:

With some slightly more complex code:

int main()
{
int x[3];
int i = 2;
x[i] = 5 + 10;

return x[i];
}

Disassembly:

Disassembly of section .text:

0000000000000000 <main>:
int main()
{
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 30             sub    $0x30,%rsp
   8:   e8 00 00 00 00          callq  d <main+0xd>
int x[3];
int i = 2;
   d:   c7 45 fc 02 00 00 00    movl   $0x2,-0x4(%rbp)
x[i] = 5 + 10;
  14:   8b 45 fc                mov    -0x4(%rbp),%eax  <== hey, could be more optimized here: movl   $0x2,%eax covers line+above line :)
  17:   48 98                   cltq
  19:   c7 44 85 f0 0f 00 00    movl   $0xf,-0x10(%rbp,%rax,4)  <== this line holds the left-operand evaluation, in a way, %rax is used to offset the array address
  20:   00

return x[i];
  21:   8b 45 fc                mov    -0x4(%rbp),%eax
  24:   48 98                   cltq
  26:   8b 44 85 f0             mov    -0x10(%rbp,%rax,4),%eax
}
  2a:   90                      nop
  2b:   48 83 c4 30             add    $0x30,%rsp
  2f:   5d                      pop    %rbp
  30:   c3                      retq
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Your marked line `x = 10` does not really align with the evaluation of the LHS - What you have marked refers to the assignment `int x = 10`. The LHS is here simply `-4(rbp)` and so trivial to evaluate that we can't see the evaluation. – tofro Aug 20 '16 at 07:23
  • 1
    you may be right. So to close the debate, I improved the post with direct mixed C/asm output. The compiler is right :) – Jean-François Fabre Aug 20 '16 at 07:27
  • You might be able to really beef up your answer by making the LHS of the assignment a bit more complicated so that we can actually *see* a bit more of its evaluation. – tofro Aug 20 '16 at 07:31
  • answer beefed up. Thanks. I'm honored by the upvotes, even if it is demonstrating how it works rather than the real explanation, given by other answers. @OP: don't accept that one :) – Jean-François Fabre Aug 20 '16 at 07:39
  • Looking at unoptimized disassembly is really a rather pointless exercise. The code is harder to read and not at all real-world. – Cody Gray - on strike Aug 21 '16 at 04:14
2

You have nontrivial expressions on the left side of = that need to be evaluated all the time. Here are some examples.

int array[5];
int *ptr = malloc(sizeof(int) * 5);

*ptr = 1;      // The lhs needs to evaluate an indirection expression
array[0] = 5;  // The lhs needs to evaluate an array subscript expression

for (int i = 0; i < 5; ++i) {
    *ptr++ = array[i];  // Both indirection and postincrement on the lhs!
}

// Here, we want to select which array element to assign to!
int test = (array[4] == 0);
(test ? array[0] : array[1]) = 5; // Both conditional and subscripting!
-1

How else would

int x, y, z;
x = y = z = 5;

work? (The assignment "z=5" has to give the (r-)value of z to the assignment "y= ...", which then has to give the value of y to the assignment "x= ...".)

The under the hood behaviour is:

  1. Load value 5 in a register (and don't reuse this register for anything else until step 7, below)
  2. Load address of z in a register (This is what "z" means when it is used as an lvalue.)
  3. Store 5 at the address of z. 5 is now the rvalue of "z". Remember CPUs work with values and addresses, not "z". The variable label "z" is a human-friendly referent to a memory address that contains a value. Depending on how it is used, we will either want its value (when we fetch the value of z) or its address (when we replace the value of z).
  4. Load address of y in a register.
  5. Store value of z (5) at the address of y. (One should/could optimize and reuse the "5" from the first step.)
  6. Load the address of x in a register.
  7. Store value of y (5) at the address of x.
Eric Towers
  • 4,175
  • 1
  • 15
  • 17
  • 1
    Assignment chaining works because assignments are expressions (not statements) in C, e.g., `z = 5` is an expression that evaluates to 5. (It also has the side effect of storing the value in `z`.) But this really doesn't have anything to do with the OP's question. – Paul J. Lucas Aug 20 '16 at 20:15
  • @PaulJ.Lucas : You don't seem to want to answer OP's question, "What's the point of evaluating the left operand?", to which he arrives from the Standard language "An assignment expression has the value of the left operand after the assignment..." The answer to which, as you correctly point out, is what I wrote. – Eric Towers Aug 20 '16 at 23:46
  • My comment was not intended for the OP whose question was already answered by R Sahu. My comment was in response to your "answer" alone that has, as I pointed out, nothing to do with either the OP's question or the true answer. – Paul J. Lucas Aug 21 '16 at 06:00
  • @EricTowers Your example of assignment chaining involves repeatedly evaluating the *right*-hand side of assignment. The OP's question is about evaluating the *left*-hand side. – jamesdlin Aug 21 '16 at 12:01
  • @jamesdlin : Incorrect. Your claim would duplicate side effects in the right-most subexpression. This contradicts the OP's quoted text: "... has the value of the left operand after the assignment". Try it: `x=y=z++;`. How many times is `z` incremented? For the `x=((temp))` step in the evaluation, the rvalue we compute is not `z++`, it's the new `y` (or we remember it from the nested assignment), but that outer evaluation of `y` is incomplete until it is coerced to a type assignable to `x`. This is all about evaluating left-hand sides. – Eric Towers Aug 21 '16 at 15:18
  • @EricTowers No. Your example shows that you don't really understand this. `x=y=z++` is equivalent to `x=(y=(z++))`. `z` would never be incremented multiple times, because the expression `z++` is evaluted only once, and its result (the original value of `z`) influences the value of the subsequent assignment expressions. The *right-hand-side* of *each individual assignment* is what's important to your example of chained assignment, and it is irrelevant to the OP's question of evaluating left-hand-sides. – jamesdlin Aug 21 '16 at 21:55
  • @jamesdlin : Your words "repeatedly evaluating the right-hand side" directly contradict you. Perhaps you should have a long conversation with you about what your words mean. None of this involves me. – Eric Towers Aug 22 '16 at 12:37
  • `x=y=z` involves multiple assignment expression. In *each* of those assignment expressions, the right-hand side is evaluated to make chaining work. Evaluation of the left-hand side of each assignment is not crucial for chaining to work. Evaluation of the left-hand side of each assignment is necessary when the left-hand side is an expression (such as in the other answers), which is not the case in your chaining examples. – jamesdlin Aug 22 '16 at 21:32
  • @jamesdlin : Congratulations. You have now arrived at the parenthetical comment in my step 5. Would you now like to go the other way and be convincing that you *have* read the answer you imagine you are criticizing? You might like to further contemplate how this would work if the types of `x`, `y`, and `z` were different, requiring promotion. In this case, according to the standard, *who*'s being evaluated? (Helpful hint: the relevant text is quoted by OP. It's the left member.) – Eric Towers Aug 23 '16 at 15:15
  • We're not saying that the left-hand-side doesn't get evaluated. We're saying that *your example of chained assignment* does not demonstrate that evaluation. Also, if type promotion were involved, it would still be applied to the right-hand-side of each assignment. The value of an expression is coerced to the type it's being assigned to; the variable isn't coerced to the type of the assigned expression. – jamesdlin Aug 23 '16 at 20:27
  • Maybe this will be clearer: your example demonstrates evaluation of the left-hand-side of assignment as much as `z = 5; y = z; x = y;` would. – jamesdlin Aug 23 '16 at 20:35
  • @jamesdlin : Congratulations. You have now shown that my answer is responsive to OP's question. `z` is the LHS of `z=5` and must be evaluated for `y=z`, `y` is the LHS of `y=z` and must be evaluated for `x=y`. And yet you say contradicting things: "if type promotion were involved, it would still be applied to the right-hand-side of each assignment". In `x=y=z` conversion of `(y=z)` to `x` is conversion of the LHS of the subexpression, `y` to the type of `x`, exactly as I wrote 7 comments back. You intermittently contradict yourself, writing that it is conversion of `z` to the type of `x`. – Eric Towers Aug 24 '16 at 12:59
  • `z` is the LHS of `z=5`, but `z` is the RHS of `y=z` which is a separate expression. Your example does not demonstrate the need to evaluate `z` when it's being used on the LHS of an assignment. Furthermore, in `x=y=z` (which is equivalent to `x=(y=z)`), `x` is *not* assigned to the evaluated value of `y`; it is assigned the value of *the assignment expression* `y=z`. You don't seem to understand this distinction, but it's something that matters in C++ where the assignment operator is allowed to return something else. – jamesdlin Aug 24 '16 at 17:40
  • @jamesdlin : I reiterate my position: you wilfully refuse to understand the OP's quoted material: "An assignment expression has the value of the left operand after the assignment". Additionally, you seem to misunderstand that this is a quote from the C standard, not the C++ standard. Perhaps this would be easier for you to notice if you were to check the OP's tags. In any event, you have adequately proven to me that you do not know what you are talking about. – Eric Towers Aug 26 '16 at 13:15
  • Of course the value of an assignment expression is the value of the left operand *after assignment* in C. I am not disputing that. I am disputing that your example is relevant to the OP's question. See the other answers for proper examples of where the left-hand-side must be evaluated. Note the phrase *after assignment*; in your example, evaluation of the left-hand-side *prior to assignment* is not important. – jamesdlin Aug 26 '16 at 13:24