C# “中是否有内联运算符的解释?”;k+;=c+;=k+;=c&引用;?
对以下操作的结果有何解释C# “中是否有内联运算符的解释?”;k+;=c+;=k+;=c&引用;?,c#,cil,compound-assignment,C#,Cil,Compound Assignment,对以下操作的结果有何解释 k += c += k += c; 我试图理解以下代码的输出结果: int k = 10; int c = 30; k += c += k += c; //k=80 instead of 110 //c=70 目前我正在努力理解为什么“k”的结果是80。为什么分配k=40不起作用(实际上VisualStudio告诉我该值没有在其他地方使用) 为什么K80不是110 如果我将操作拆分为: k+=c; c+=k; k+=c; 结果是k=110 我试着看完,但我对解释生
k += c += k += c;
我试图理解以下代码的输出结果:
int k = 10;
int c = 30;
k += c += k += c;
//k=80 instead of 110
//c=70
目前我正在努力理解为什么“k”的结果是80。为什么分配k=40不起作用(实际上VisualStudio告诉我该值没有在其他地方使用)
为什么K80不是110
如果我将操作拆分为:
k+=c;
c+=k;
k+=c;
结果是k=110
我试着看完,但我对解释生成的CIL不太深刻,无法获得一些细节:
// [11 13 - 11 24]
IL_0001: ldc.i4.s 10
IL_0003: stloc.0 // k
// [12 13 - 12 24]
IL_0004: ldc.i4.s 30
IL_0006: stloc.1 // c
// [13 13 - 13 30]
IL_0007: ldloc.0 // k expect to be 10
IL_0008: ldloc.1 // c
IL_0009: ldloc.0 // k why do we need the second load?
IL_000a: ldloc.1 // c
IL_000b: add // I expect it to be 40
IL_000c: dup // What for?
IL_000d: stloc.0 // k - expected to be 40
IL_000e: add
IL_000f: dup // I presume the "magic" happens here
IL_0010: stloc.1 // c = 70
IL_0011: add
IL_0012: stloc.0 // k = 80??????
像
a op=b这样的操作代码>相当于a=a op b代码>。赋值可以用作语句或表达式,而作为表达式它会生成赋值。你的陈述
k += c += k += c;
。。。由于赋值运算符是右关联的,因此也可以写为
k += (c += (k += c));
或(扩展)
在整个评估过程中,使用相关变量的旧值。这对于k
的值尤其如此(请参阅我对下面的IL和提供的Wai Ha Lee的评论)。因此,您得到的不是70+40(新值k
)=110,而是70+10(旧值k
)=80
要点是(根据C#)“表达式中的操作数是从左到右求值的”(在我们的例子中,操作数是变量C
和k
)。这与运算符优先级和关联性无关,在本例中,运算符优先级和关联性指示从右到左的执行顺序。(参见本页对Eric Lippert的评论)
现在让我们看一下IL。IL假设一个基于堆栈的虚拟机,即它不使用寄存器
IL_0007: ldloc.0 // k (is 10)
IL_0008: ldloc.1 // c (is 30)
IL_0009: ldloc.0 // k (is 10)
IL_000a: ldloc.1 // c (is 30)
堆栈现在看起来是这样的(从左到右;堆栈顶部是右侧)
10301030
103040
10304040
1070
107070
八十
它归结为:第一个+=
是应用于原始k
还是应用于计算得更偏右的值
答案是,虽然任务从右向左绑定,但操作仍然从左向右进行
所以最左边的+=
正在执行10+=70
首先,亨克和奥利弗的答案是正确的;我想用一种稍微不同的方式来解释它。具体来说,我想谈谈你提出的这一点。您有以下一组语句:
int k = 10;
int c = 30;
k += c += k += c;
int k = 10;
int c = 30;
k += c;
c += k;
k += c;
然后,您错误地得出结论,这应该与这组语句得出相同的结果:
int k = 10;
int c = 30;
k += c += k += c;
int k = 10;
int c = 30;
k += c;
c += k;
k += c;
了解您是如何出错的,以及如何正确操作的,这将提供信息。正确的分解方法是这样的
首先,重写最外层+=
k = k + (c += k += c);
其次,重写最外层的+我希望您同意x=y+z必须始终与“将y计算为临时值,将z计算为临时值,将临时值相加,将总和分配给x”相同。。所以,让我们非常明确地说:
int t1 = k;
int t2 = (c += k += c);
k = t1 + t2;
确保这是清楚的,因为这是您错误的步骤。当将复杂的操作分解为简单的操作时,您必须确保操作缓慢且小心,并且不要跳过步骤。跳过步骤是我们犯错误的地方
好的,现在把作业分解成t2,再一次,慢慢地,仔细地
int t1 = k;
int t2 = (c = c + (k += c));
k = t1 + t2;
赋值将给t2赋值,与赋值给c的赋值相同,因此假设:
int t1 = k;
int t2 = c + (k += c);
c = t2;
k = t1 + t2;
太好了。现在分解第二行:
int t1 = k;
int t3 = c;
int t4 = (k += c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
太好了,我们正在取得进展。将分配细分为t4:
int t1 = k;
int t3 = c;
int t4 = (k = k + c);
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
现在分解第三行:
int t1 = k;
int t3 = c;
int t4 = k + c;
k = t4;
int t2 = t3 + t4;
c = t2;
k = t1 + t2;
现在我们可以看看整个事情:
int k = 10; // 10
int c = 30; // 30
int t1 = k; // 10
int t3 = c; // 30
int t4 = k + c; // 40
k = t4; // 40
int t2 = t3 + t4; // 70
c = t2; // 70
k = t1 + t2; // 80
当我们完成时,k是80,c是70
现在让我们看看这是如何在IL中实现的:
int t1 = k;
int t3 = c;
is implemented as
ldloc.0 // stack slot 1 is t1
ldloc.1 // stack slot 2 is t3
现在这有点棘手:
int t4 = k + c;
k = t4;
is implemented as
ldloc.0 // load k
ldloc.1 // load c
add // sum them to stack slot 3
dup // t4 is stack slot 3, and is now equal to the sum
stloc.0 // k is now also equal to the sum
我们本可以按要求实施上述内容
ldloc.0 // load k
ldloc.1 // load c
add // sum them
stloc.0 // k is now equal to the sum
ldloc.0 // t4 is now equal to k
但是我们使用“dup”技巧,因为它使代码更短,更容易抖动,我们得到了相同的结果一般来说,C#代码生成器会尽可能地将临时代码保持在堆栈上的“临时”状态。如果您发现使用较少的临时代码更容易遵循IL,请关闭优化,代码生成器的攻击性就会降低
我们现在必须用同样的技巧得到c:
int t2 = t3 + t4; // 70
c = t2; // 70
is implemented as:
add // t3 and t4 are the top of the stack.
dup
stloc.1 // again, we do the dup trick to get the sum in
// both c and t2, which is stack slot 2.
最后:
k = t1 + t2;
is implemented as
add // stack slots 1 and 2 are t1 and t2.
stloc.0 // Store the sum to k.
因为我们不需要其他任何东西的总和,我们不重复它。堆栈现在是空的,我们在语句的末尾
这个故事的寓意是:当你试图理解一个复杂的程序时,一定要一次分解一个操作。不要走捷径;它们会把你引入歧途。你可以通过数数来解决这个问题
a = k += c += k += c
有两个c
s和两个k
s所以
a = 2c + 2k
而且,作为语言运算符的结果,k
也等于2c+2k
这将适用于此类型链中的任何变量组合:
a = r += r += r += m += n += m
所以
和r
将等于相同的值
只需计算最左边的赋值,就可以算出其他数字的值。所以m
等于2m+n
和n
等于n+m
这表明k+=c+=k+=c
不同于k+=c;c+=k;k+=c代码>以及为什么会得到不同的答案
评论中的一些人似乎担心,您可能会试图将此快捷方式过度概括为所有可能的添加类型。因此,我要明确指出,此快捷方式仅适用于这种情况,即将内置数字类型的加法赋值链接在一起。如果在中添加其他运算符,例如()
或+
,或者调用函数,或者覆盖了+=
,或者使用的不是基本的数字类型,则不一定有效它仅用于帮助解决问题中的特定情况int t1 = k;
int t3 = c;
is implemented as
ldloc.0 // stack slot 1 is t1
ldloc.1 // stack slot 2 is t3
int t4 = k + c;
k = t4;
is implemented as
ldloc.0 // load k
ldloc.1 // load c
add // sum them to stack slot 3
dup // t4 is stack slot 3, and is now equal to the sum
stloc.0 // k is now also equal to the sum
ldloc.0 // load k
ldloc.1 // load c
add // sum them
stloc.0 // k is now equal to the sum
ldloc.0 // t4 is now equal to k
int t2 = t3 + t4; // 70
c = t2; // 70
is implemented as:
add // t3 and t4 are the top of the stack.
dup
stloc.1 // again, we do the dup trick to get the sum in
// both c and t2, which is stack slot 2.
k = t1 + t2;
is implemented as
add // stack slots 1 and 2 are t1 and t2.
stloc.0 // Store the sum to k.
a = k += c += k += c
a = 2c + 2k
a = r += r += r += m += n += m
a = 2m + n + 3r
int k = 10;
int c = 30;
k += c += k += c;
10 += 30 += 10 += 30
= 10 + 30 + 10 + 30
= 80 !!!
k = 10;
c = 30;
k = c+k;
c = c+k;
k = c+k;