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

一个人想着一个人 提交于 2020-01-12 06:47:07

问题


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?


回答1:


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.




回答2:


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.




回答3:


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



回答4:


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!



回答5:


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.


来源:https://stackoverflow.com/questions/39051512/whats-the-point-of-evaluating-left-operand-of-assignment-operator-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!