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)的原值"
为什么会这样?你可以在这里找到很好的外貌
简而言之
希望有帮助。
更新根据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! |
有关更多未定义的行为,请参见此处:为什么这些构造(使用++)未定义的行为?