问题
According to ISO C11 - 6.5.16.3, it says that
- 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;
- Left operand
x
is evaluated to 10 and right operand is evaluated to 15. - 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:
- Load value 5 in a register (and don't reuse this register for anything else until step 7, below)
- Load address of
z
in a register (This is what "z
" means when it is used as an lvalue.) - 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 ofz
) or its address (when we replace the value ofz
). - Load address of
y
in a register. - Store value of
z
(5) at the address ofy
. (One should/could optimize and reuse the "5" from the first step.) - Load the address of
x
in a register. - Store value of
y
(5) at the address ofx
.
来源:https://stackoverflow.com/questions/39051512/whats-the-point-of-evaluating-left-operand-of-assignment-operator-in-c