在 C 中评估赋值运算符的左操作数有什么意义?

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

根据 ISO C11 - 6.5.16.3,它表示

  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.

所以我想这意味着,例如,

int x = 10;
x = 5 + 10;
  1. 左操作数 x 的计算结果为 10,右操作数的计算结果为 15。
  2. 右操作数的值存储在左操作数指定的对象中x

但是如果赋值的目的是存储右操作数的计算值(就像在步骤2中一样),为什么左操作数的计算是必要的?评估左操作数有什么意义?

x 被评估为 lvalue 时,它不会评估为 10。它评估为 lvalue 可以存储 RHS 值的地方。如果 LHS 的计算结果不是 lvalue,则该语句将出错。

来自 C99 标准 (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.

当您有一个简单的变量时,将 LHS 计算为 lvalue 是微不足道的,例如

 x = 10;

但是,它可能更复杂。

 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

如果 getIndex() returns 5,则 LHS 计算为指定数组第 7 个元素的 lvalue

A "left operand" 可能比您示例中的简单 x 复杂得多(诚然,评估起来并不是真正的挑战):

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

确实需要对 LHS 进行一些评估。您引用的句子是指需要在作业的左侧完成哪些操作才能找到合适的 lvalue 来接收作业。

只是为了从 "Judas" 的角度来说服自己(如果还没有完成的话),这证明我的 post 只回答了你这个简单案例中的简单问题。

证明在您的简单示例中 gcc 只做它需要的事情,而不是更多:

代码:

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

return x;
}

调试构建

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

带有混合 C/asm 代码的 objdump

K:\jff\data\python\Whosebug\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    [=12=]x30,%rsp
   8:   e8 00 00 00 00          callq  d <main+0xd>
int x = 10;
   d:   c7 45 fc 0a 00 00 00    movl   [=12=]xa,-0x4(%rbp)
x = 5 + 10;
  14:   c7 45 fc 0f 00 00 00    movl   [=12=]xf,-0x4(%rbp)

return x;
  1b:   8b 45 fc                mov    -0x4(%rbp),%eax
}
  1e:   90                      nop
  1f:   48 83 c4 30             add    [=12=]x30,%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

正如其他(好的)答案中所述,不愿意解释,但如果表达式更复杂,则必须计算存储值的地址,因此需要某种评估。

编辑:

使用一些稍微复杂的代码:

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

return x[i];
}

反汇编:

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    [=14=]x30,%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   [=14=]x2,-0x4(%rbp)
x[i] = 5 + 10;
  14:   8b 45 fc                mov    -0x4(%rbp),%eax  <== hey, could be more optimized here: movl   [=14=]x2,%eax covers line+above line :)
  17:   48 98                   cltq
  19:   c7 44 85 f0 0f 00 00    movl   [=14=]xf,-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    [=14=]x30,%rsp
  2f:   5d                      pop    %rbp
  30:   c3                      retq

否则

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

工作? (赋值“z=5”必须将 z 的 (r-) 值赋给赋值“y= ...”,然后它必须将 y 的值赋给作业“x= ...”。)

引擎盖下的行为是:

  1. 在寄存器中加载值 5(在下面的第 7 步之前不要将此寄存器重新用于任何其他用途)
  2. 在寄存器中加载 z 的地址(这就是“z”用作左值时的意思。)
  3. 将5存入z的地址。 5 现在是“z”的右值。请记住,CPU 使用值和地址,而不是“z”。变量标签“z”是对包含值的内存地址的人性化引用。根据它的使用方式,我们要么想要它的值(当我们获取 z 的值时),要么它的地址(当我们替换 z 的值时)。
  4. 在寄存器中加载 y 的地址。
  5. z (5) 的值存储在 y 的地址。 (一个 should/could 优化并重用第一步中的“5”。)
  6. 在寄存器中加载 x 的地址。
  7. y(5)的值存储在x的地址。

您在 = 的左侧有重要的表达式,需要一直计算。这里有一些例子。

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!