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?

2
  • where does it say the left operand is evaluated? Commented Aug 20, 2016 at 7:12
  • "after the value computations of the left ..." Commented Aug 20, 2016 at 7:13

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Can you explain me in detail what it means to be evaluated to an lvalue?
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?
Wow I have never heard of such concept, evaluating to an object. Do you know any good references about that topic?
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.
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.
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.

Comments

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

5 Comments

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.
you may be right. So to close the debate, I improved the post with direct mixed C/asm output. The compiler is right :)
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.
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 :)
Looking at unoptimized disassembly is really a rather pointless exercise. The code is harder to read and not at all real-world.
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!

Comments

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

15 Comments

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.
@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.
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.
@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 : 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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.