在 C 中评估赋值运算符的左操作数有什么意义?
What's the point of evaluating left operand of assignment operator in C?
根据 ISO C11 - 6.5.16.3,它表示
- 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;
- 左操作数
x
的计算结果为 10,右操作数的计算结果为 15。
- 右操作数的值存储在左操作数指定的对象中
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= ...
”。)
引擎盖下的行为是:
- 在寄存器中加载值 5(在下面的第 7 步之前不要将此寄存器重新用于任何其他用途)
- 在寄存器中加载
z
的地址(这就是“z
”用作左值时的意思。)
- 将5存入
z
的地址。 5 现在是“z
”的右值。请记住,CPU 使用值和地址,而不是“z
”。变量标签“z
”是对包含值的内存地址的人性化引用。根据它的使用方式,我们要么想要它的值(当我们获取 z
的值时),要么它的地址(当我们替换 z
的值时)。
- 在寄存器中加载
y
的地址。
- 将
z
(5) 的值存储在 y
的地址。 (一个 should/could 优化并重用第一步中的“5”。)
- 在寄存器中加载
x
的地址。
- 将
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!
根据 ISO C11 - 6.5.16.3,它表示
- 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;
- 左操作数
x
的计算结果为 10,右操作数的计算结果为 15。 - 右操作数的值存储在左操作数指定的对象中
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= ...
”。)
引擎盖下的行为是:
- 在寄存器中加载值 5(在下面的第 7 步之前不要将此寄存器重新用于任何其他用途)
- 在寄存器中加载
z
的地址(这就是“z
”用作左值时的意思。) - 将5存入
z
的地址。 5 现在是“z
”的右值。请记住,CPU 使用值和地址,而不是“z
”。变量标签“z
”是对包含值的内存地址的人性化引用。根据它的使用方式,我们要么想要它的值(当我们获取z
的值时),要么它的地址(当我们替换z
的值时)。 - 在寄存器中加载
y
的地址。 - 将
z
(5) 的值存储在y
的地址。 (一个 should/could 优化并重用第一步中的“5”。) - 在寄存器中加载
x
的地址。 - 将
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!