What's the point of evaluating left operand of assignment operator in C?
Asked Answered
B

5

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?

Besot answered 20/8, 2016 at 7:9 Comment(2)
where does it say the left operand is evaluated?Toscanini
"after the value computations of the left ..."Outline
M
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.

Mo answered 20/8, 2016 at 7:14 Comment(5)
Can you explain me in detail what it means to be evaluated to an lvalue?Besot
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?Besot
Wow I have never heard of such concept, evaluating to an object. Do you know any good references about that topic?Besot
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.Mo
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.Cavern
S
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.

Shellfish answered 20/8, 2016 at 7:16 Comment(0)
W
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
Wandie answered 20/8, 2016 at 7:17 Comment(5)
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.Shellfish
you may be right. So to close the debate, I improved the post with direct mixed C/asm output. The compiler is right :)Outline
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.Shellfish
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 :)Outline
Looking at unoptimized disassembly is really a rather pointless exercise. The code is harder to read and not at all real-world.Marigolda
N
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!
Naturally answered 21/8, 2016 at 2:44 Comment(0)
C
-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.
Chappie answered 20/8, 2016 at 19:49 Comment(15)
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.Cavern
@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.Chappie
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.Cavern
@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.Deane
@Deane : 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.Chappie
@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.Deane
@Deane : 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.Chappie
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.Deane
@Deane : 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.)Chappie
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.Deane
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.Deane
@Deane : 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.Chappie
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.Deane
@Deane : 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.Chappie
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.Deane

© 2022 - 2024 — McMap. All rights reserved.