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

根据ISO C11 – 6.5.16.3,它说

  1. 赋值运算符将值存储在左操作数指定的对象中。 赋值表达式在赋值后具有左操作数的值,但不是左值。 赋值表达式的类型是左值操作数在左值转换后将具有的类型。 在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。 对操作数的评估是不确定的。

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

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

但是如果赋值的目的是存储右操作数的evalauted值(就像在step2中那样),为什么左操作数的评估是必要的? 评估左操作数有什么意义?

x被评估为lvalue ,它不会求值为10.它计算为可以存储RHS值的lvalue值。 如果LHS没有评估为lvalue ,则该语句将是错误的。

根据C99标准(6.3.2.1/1):

左值是一个表达式(对象类型不是void)可能指定一个对象; 如果左值在评估时未指定对象,则行为未定义。

当你有一个简单的变量时,将LHS评估为左值是微不足道的

  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()返回5 ,则LHS求值为指定数组第7个元素的左值

在您的示例中,“左操作数”可能比您的简单x复杂得多(这无疑是评估的真正挑战):

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

肯定需要对LHS进行一些评估。 您引用的句子指的是在作业的左侧需要做什么,以便找到适当的左值来接收作业。

只是为了说服自己(如果还没有完成)从“犹大”的角度来看,这certificate我的post只能回答你简单案例中的简单问题。

小certificate表明,在你的简单例子中,gcc只需要它,而不是更多:

码:

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

使用debug构建

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

具有混合C / asm代码的objdump

 K:\jff\data\python\stackoverflow\c>objdump -d -S assign.o assign.o: file format pe-x86-64 Disassembly of section .text: 0000000000000000 
: 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 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

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

编辑:

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

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

拆卸:

 Disassembly of section .text: 0000000000000000 
: 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 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

您在=的左侧有非常重要的表达式,需要一直进行评估。 这里有些例子。

 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! 

怎么会

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

工作? (赋值“ z=5 ”必须将z=5的(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的地址处。 (从第一步开始,应该/可以优化和重用“5”。)
  6. x的地址加载到寄存器中。
  7. y (5)的值存储在x的地址处。