C#在多次赋值中丢失中间值


C# loss of intermediate values in multiple assignment

鉴于。。。

1
int a = 1, b = 4;

然后…

1
a += b += a += b;

在C++中被评估…

1
(a += (b += (a += b))); // a = 14[5 + 9] (b = 9[4 + 5] (a = 5[1 + 4]))

…在C…

1
2
3
4
(a += (b += (a += b))); // a = 10[1 + 9] (b = 9[4 + 5] (a = 5[1 + 4]))  
                        //        ^ re-use of original value of a (1)  
                        //          instead of expected intermediate right-to-left  
                        //          evaluation result (5)

上面的测试已经在Visual Studio 2008和2012中进行了测试,因此没有怀疑最近引入的语言错误。

不过,我确实希望C的行为能模仿C++,并且假设我需要教育。我了解表达式的计算逻辑,不需要解释MSIL。经过相当广泛的搜索,在语言规范中找不到相关的细节,我希望语言专家能够解释为什么会这样。

对于那些想知道我到底为什么要这样做的好奇者…有一个简单的C++技巧,用于一个非常高效和整洁的两个整数类型的内联交换。

1
a ^= b ^= a ^= b;

我很失望地发现它在C语言中不起作用,并且好奇为什么。这是关于理解C的低级力学及其背后的基本原理。不多,不少,特别是不可读性宗教请。

铌拜托,伙计们,这不是把所有东西都挤在一条线上的一次认真的努力!!!!对于C(和后来的C++),算子的优先性和关联性一直是精确定义的。赋值操作符的标准右到左关联性使得C/C++版本的行为100%清晰且可预测。它已经在几个编译器和平台上为我工作了,我会认为一个不这样做的编译器是错误的。我承认代码的代码是模糊的,它是一种好奇心,被用来作为一个"脑筋急转弯",我可以给一个初级C/C++程序员。显然,C是不同的——显然是故意的。我寻求一个设计考虑的解释,导致C的行为与它的祖先语言之一的分歧。即使是受过教育的猜测也会受到欢迎。

回答多亏了亚历山大斯蒂芬尼克。从左到右开始计算操作数

1
2
3
4
5
a        = 1 + theRest1     // (1)  
theRest1 = b = 4 + theRest2 // (2)  
theRest2 = a = 1 + 4        // (3)  
(3) into (2): theRest1 = b = 4 + 5  
(2) into (1): a = 1 + 9

一个"为什么"的解释仍然值得赞赏。但C规范清楚地表明,上述评估是正确的。下面我们可以用2个变量来接近C++技巧…

1
2
b ^= a ^= b;
a ^ = b;

我不喜欢它,因为它打破了我的直觉。


它也不能保证在C/C++中工作。在单个语句中多次赋值的变量值未定义。

它的工作归根结底是实现——您可能会发现一个编译器或体系结构根本不工作。

C只是严格要求,这不是坏事。

无论如何,XOR技巧在现代建筑中毫无意义。底层实现的效率将低于仅使用临时变量的效率。现代CPU上有两个以上的寄存器。


你真的自己解释了每件事:)

"重新使用A(1)的原值"

为什么会这样?你可以在这里找到很好的外貌

简而言之a += anything相当于a = a + anything。因此,编译器需要计算表达式a + anything的两个操作数,然后将结果赋给a:1。计算第一个操作数(即a)。评价结果为12。计算第二个操作数(即expression)。评价结果为9。评价的副作用是a含有5个三。计算第一个和第二个操作数的和。结果是104。结果分配给a。现在,a包含10个。

希望有帮助。

更新根据C规范:表达式中操作数的计算顺序是从左到右。见第150页第14.2节。因此,这种行为在c中被指定和纠正。


它甚至可以追溯到C。

K&R有以下示例:

1
a[i++] = i;

这是未定义的行为,在一行中修改和使用变量。一个编译器可以以各种方式工作。这个标准谨慎地保留了它的定义,这样您就不需要依赖它了。

K&R建议使用单独的行或中间变量。不是为了人类的可读性,而是为了使编译器的解释更清晰。

1
a ^= b ^= a ^= b;

所以这就变得明确了:

1
a ^= b; b ^= a; a ^= b; // can still on one line if that is important to you!

有关更多未定义的行为,请参见此处:为什么这些构造(使用++)未定义的行为?